本文由 SnailClimb 和 guang19 共同完成。
本文由 SnailClimb 和 guang19 共同完成。
关于事务基本概览的介绍,请看这篇文章的介绍:MySQL 常见知识点&面试题总结
事务隔离级别总结
SQL 标准定义了四种事务隔离级别,用来平衡事务的隔离性(Isolation)和并发性能。级别越高,数据一致性越好,但并发性能可能越低。这四个级别是:
READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
READ-COMMITTED(读取已提交) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server)的默认隔离级别。
REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且,InnoDB 在此级别下通过 MVCC(多版本并发控制) 和 Next-Key Locks(间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
SERIALIZABLE(可串行化) :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别脏读 (Dirty Read)不可重复读 (Non-Repeatable Read)幻读 (Phantom Read)READ UNCOMMITTED√√√READ COMMITTED×√√REPEATABLE READ××√ (标准) / ≈× (InnoDB)SERIALIZABLE×××
隔离级别脏读 (Dirty Read)不可重复读 (Non-Repeatable Read)幻读 (Phantom Read)
隔离级别
脏读 (Dirty Read)
不可重复读 (Non-Repeatable Read)
幻读 (Phantom Read)
READ UNCOMMITTED√√√
READ UNCOMMITTED
√
√
√
READ COMMITTED×√√
READ COMMITTED
×
√
√
REPEATABLE READ××√ (标准) / ≈× (InnoDB)
REPEATABLE READ
×
×
√ (标准) / ≈× (InnoDB)
SERIALIZABLE×××
SERIALIZABLE
×
×
×
默认级别查询:
MySQL InnoDB 存储引擎的默认隔离级别是 REPEATABLE READ。可以通过以下命令查看:
MySQL 8.0 之前:SELECT @@tx_isolation;
SELECT @@tx_isolation;
MySQL 8.0 及之后:SELECT @@transaction_isolation;
SELECT @@transaction_isolation;
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
InnoDB 的 REPEATABLE READ 对幻读的处理:
标准的 SQL 隔离级别定义里,REPEATABLE READ 是无法防止幻读的。但 InnoDB 的实现通过以下机制很大程度上避免了幻读:
快照读 (Snapshot Read):普通的 SELECT 语句,通过 MVCC 机制实现。事务启动时创建一个数据快照,后续的快照读都读取这个版本的数据,从而避免了看到其他事务新插入的行(幻读)或修改的行(不可重复读)。
当前读 (Current Read):像 SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, INSERT, UPDATE, DELETE 这些操作。InnoDB 使用 Next-Key Lock 来锁定扫描到的索引记录及其间的范围(间隙),防止其他事务在这个范围内插入新的记录,从而避免幻读。Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的组合。
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
INSERT
UPDATE
DELETE
值得注意的是,虽然通常认为隔离级别越高、并发性越差,但 InnoDB 存储引擎通过 MVCC 机制优化了 REPEATABLE READ 级别。对于许多常见的只读或读多写少的场景,其性能与 READ COMMITTED 相比可能没有显著差异。不过,在写密集型且并发冲突较高的场景下,RR 的间隙锁机制可能会比 RC 带来更多的锁等待。
此外,在某些特定场景下,如需要严格一致性的分布式事务(XA Transactions),InnoDB 可能要求或推荐使用 SERIALIZABLE 隔离级别来确保全局数据的一致性。
《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章这样写到:
InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
实际情况演示
在下面我会使用 2 个命令行 MySQL ,模拟多线程(多事务)对同一份数据的脏读问题。
MySQL 命令行的默认配置中事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:START TRANSACTION。
START TRANSACTION
我们可以通过下面的命令来设置隔离级别。
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:
START TRANSACTION |BEGIN:显式地开启一个事务。
START TRANSACTION
BEGIN
COMMIT:提交事务,使得对数据库做的所有修改成为永久性。
COMMIT
ROLLBACK:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
ROLLBACK
脏读(读未提交)
实例.jpg)
避免脏读(读已提交)

不可重复读
还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。

可重复读

幻读
演示幻读出现的情况

SQL 脚本 1 在第一次查询工资为 500 的记录时只有一条,SQL 脚本 2 插入了一条工资为 500 的记录,提交之后;SQL 脚本 1 在同一个事务中再次使用当前读查询发现出现了两条工资为 500 的记录这种就是幻读。
注:这个例子本质上是第一次快照读和第二次当前读的读语义不同。RR 下,MVCC 可以保证快照读不出现幻读,Next-Key Lock 可以约束当前读;但同一事务内混用快照读和当前读时,两次读取看到的结果可能不同。
解决幻读的方法
解决幻读的方式有很多,但是它们的核心思想就是一个事务在操作某张表数据的时候,另外一个事务不允许新增或者删除这张表中的数据了。解决幻读的方式主要有以下几种:
将事务隔离级别调整为 SERIALIZABLE 。
SERIALIZABLE
在可重复读的事务级别下,给事务操作的这张表添加表锁。
在可重复读的事务级别下,给事务操作的这张表添加 Next-key Lock(Record Lock+Gap Lock)。
Next-key Lock(Record Lock+Gap Lock)
参考
《MySQL 技术内幕:InnoDB 存储引擎》
https://dev.MySQL.com/doc/refman/5.7/en/
https://dev.MySQL.com/doc/refman/5.7/en/
Mysql 锁:灵魂七拷问
Innodb 中的事务隔离级别和锁的关系
写在最后
如果内容对你有帮助的话,欢迎顺手给 JavaGuide 点一个免费的 Star 支持一下:GitHub | Gitee。
JavaGuide 已持续维护近七年,累计 6100+ 次提交,来自 620+ 位贡献者共同完善。你的 Star、反馈和 PR,都是这个项目继续更新的动力。
如果你正在准备后端/AI 应用开发面试,也可以了解一下我的知识星球,里面包括后端和 AI 实战项目、简历优化、一对一提问和高频考点资料,已经持续维护六年。