Mysql的事务隔离是通过不同的隔离级别来控制多个事务同时操作数据时的一致性问题,避免出现脏读、不可重复读、幻读等异常情况,其实现依赖锁机制和MVCC多版本并发控制两种核心技术。

事务隔离级别
Mysql支持四种标准的事务隔离级别,不同级别解决的并发问题不同,级别越高数据一致性越好,但并发性能会有所下降,四种级别如下:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED(读未提交) | 存在 | 存在 | 存在 |
| READ COMMITTED(读已提交) | 不存在 | 存在 | 存在 |
| REPEATABLE READ(可重复读) | 不存在 | 不存在 | 不存在(InnoDB引擎通过间隙锁解决) |
| SERIALIZABLE(串行化) | 不存在 | 不存在 | 不存在 |
可以通过如下语句查看和设置当前会话的事务隔离级别:
-- 查看当前会话隔离级别 SELECT @@transaction_isolation; -- 设置当前会话隔离级别为可重复读 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
锁机制实现事务隔离
Mysql的InnoDB引擎通过不同类型的锁来控制并发访问,是事务隔离的基础实现方式:
行锁与表锁
行锁是锁定某一行数据的锁,不同事务可以同时操作不同行的数据,并发性能更好;表锁是锁定整张表,同一时间只有一个事务能操作表,并发性能较差。InnoDB默认使用行锁,MyISAM引擎只支持表锁。
共享锁与排他锁
- 共享锁(S锁):事务读取数据时加的锁,多个事务可以同时持有同一行数据的共享锁,但是不能加排他锁。
- 排他锁(X锁):事务修改数据时加的锁,加了排他锁的数据其他事务不能加任何锁,也不能读取修改。
可以通过以下语句手动加锁:
-- 加共享锁 SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 加排他锁 SELECT * FROM user WHERE id = 1 FOR UPDATE;
间隙锁
间隙锁是InnoDB在REPEATABLE READ级别下为了解决幻读新增的锁,锁定的是索引记录之间的间隙,防止其他事务在这个间隙插入新数据。比如执行SELECT * FROM user WHERE id > 10 AND id < 20 FOR UPDATE时,会锁定id在10到20之间的所有间隙,其他事务无法插入id在这个范围的记录。
MVCC实现事务隔离
MVCC即多版本并发控制,是InnoDB实现READ COMMITTED和REPEATABLE READ级别的核心机制,它不需要加锁就能实现非阻塞的读操作,提升并发性能。
核心原理
MVCC通过在每行数据后保存两个隐藏列实现:trx_id记录最后一次修改该行的事务ID,roll_pointer指向该行之前的版本在undo log中的位置。每次修改数据时,都会生成一个新的版本,旧版本保存在undo log中。
Read View
Read View是事务执行读操作时生成的快照,里面记录了当前活跃的事务ID列表、最小活跃事务ID、最大事务ID等信息。读数据时会对比数据的trx_id和Read View的信息,判断哪个版本的数据对当前事务可见:
- 如果数据的trx_id小于Read View的最小活跃事务ID,说明该版本数据是已提交事务修改的,可见。
- 如果数据的trx_id在活跃事务ID列表中,说明修改该数据的事务还未提交,不可见,需要找旧版本。
- 如果数据的trx_id大于Read View的最大事务ID,说明修改该数据的事务是在当前Read View生成之后才开始的,不可见,需要找旧版本。
READ COMMITTED级别下,每次读操作都会生成新的Read View,所以能读到其他事务最新提交的数据;REPEATABLE READ级别下,事务第一次读操作时生成Read View,之后每次读都复用这个Read View,所以能保证多次读到的数据一致。
不同隔离级别示例
创建测试表并插入数据:
CREATE TABLE test_isolation ( id INT PRIMARY KEY, num INT ) ENGINE=InnoDB; INSERT INTO test_isolation VALUES (1, 10);
在READ COMMITTED级别下,事务A执行修改但未提交,事务B能读到修改前的数据,事务A提交后事务B能读到修改后的数据:
-- 事务A START TRANSACTION; UPDATE test_isolation SET num = 20 WHERE id = 1; -- 事务B(隔离级别为READ COMMITTED) START TRANSACTION; SELECT num FROM test_isolation WHERE id = 1; -- 结果为10,未读到未提交的修改 COMMIT; -- 事务A提交 COMMIT; -- 事务B再次查询 START TRANSACTION; SELECT num FROM test_isolation WHERE id = 1; -- 结果为20,读到了已提交的修改 COMMIT;
在REPEATABLE READ级别下,事务A修改提交后,事务B在同一个事务内多次查询得到的结果一致:
-- 事务A START TRANSACTION; UPDATE test_isolation SET num = 30 WHERE id = 1; COMMIT; -- 事务B(隔离级别为REPEATABLE READ) START TRANSACTION; SELECT num FROM test_isolation WHERE id = 1; -- 结果为20 SELECT num FROM test_isolation WHERE id = 1; -- 结果仍为20,即使事务A已经提交修改 COMMIT;