事务隔离级别决定的是「能看到什么」,不是「执行顺序」
MySQL 的事务隔离级别(
READ UNCOMMITTED、
READ COMMITTED、
REPEATABLE READ、
SERIALIZABLE)不控制 SQL 语句的执行先后,而是约束一个事务中
SELECT能读到哪些版本的数据。真正影响执行顺序的是锁机制、MVCC 快照生成时机和语句是否触发加锁(比如
UPDATE、
SELECT ... FOR UPDATE)。
常见误解是:设成
REPEATABLE READ就能保证两个事务按提交顺序“串行执行”。实际不是——它们仍可并发执行,只是读视图被冻结在事务第一次
SELECT或开启时,导致“读一致性”,而非“执行一致性”。
不同隔离级别下,同一时间点的 SELECT 结果为何不同
关键在事务开启时获取的 MVCC 快照(read view)范围不同:
READ UNCOMMITTED:不创建 read view,
SELECT直接读最新行版本(可能脏读)
READ COMMITTED:每次
SELECT都新建 read view,只能看到已提交的、在本语句开始前已提交的事务修改
REPEATABLE READ:事务第一次
SELECT时创建 read view,后续所有
SELECT复用它(所以不会出现不可重复读)
SERIALIZABLE:隐式为所有
SELECT加共享锁,强制阻塞写操作,实际变成串行执行
例如两个事务 T1 和 T2 同时运行:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; SELECT * FROM t WHERE id = 1; -- 此刻生成 read view -- 即使 T2 此后更新并提交 id=1 的行,T1 再 SELECT 仍看到旧值
执行顺序受锁和语句类型影响,比隔离级别更直接
即使在
REPEATABLE READ下,
UPDATE或
SELECT ... FOR UPDATE仍会尝试加行锁或间隙锁,导致阻塞。这时“谁先拿到锁”就决定了实际执行顺序,跟隔离级别无关。
INSERT可能触发插入意向锁,与间隙锁冲突
UPDATE t SET x=1 WHERE y=5在无索引列
y上会升级为表锁
SELECT ... LOCK IN SHARE MODE和
FOR UPDATE显式申请锁,立即参与锁竞争
典型现象:事务 A 执行
UPDATE t SET v=1 WHERE id=10未提交,事务 B 在相同行上执行
UPDATE或
SELECT ... FOR UPDATE就会被挂起,直到 A 提交或回滚——这个等待链就是真实执行顺序的体现。
容易忽略的关键点:autocommit 和 START TRANSACTION 的时机
MySQL 默认
autocommit = 1,单条 DML(如
UPDATE)会自动成为独立事务。此时隔离级别只作用于该语句本身,没有“跨语句一致性”可言。只有显式
START TRANSACTION或
BEGIN后,才进入多语句事务上下文,隔离级别才有持续效果。 忘记关
autocommit就直接写多个
UPDATE,误以为它们在一个事务里 在存储过程中调用
START TRANSACTION,但没处理异常回滚,导致连接长期持有锁
SET SESSION TRANSACTION ISOLATION LEVEL ...只对后续事务生效,不影响当前已开启的事务
验证当前事务状态最直接的方式是查:
SELECT @@autocommit, @@transaction_isolation, trx_state FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id = CONNECTION_ID();
