RR隔离级别下UPDATE不会触发半一致性读;该机制仅在RC下生效,RR中UPDATE为当前读并严格加锁,所谓“类似行为”多因隔离级别误判、索引失效导致的锁等待或快照读/当前读混淆所致。

UPDATE 在 RR 隔离级别下不会触发半一致性读——这是最关键的事实。如果你观察到类似行为,大概率是误判了隔离级别、执行计划或锁机制。
确认当前事务隔离级别是否真是 RR
MySQL 的半一致性读(semi-consistent read)只在 RC 隔离级别下生效,RR 下完全不启用。
哪怕你看到 SELECT @@transaction_isolation 返回 'REPEATABLE-READ',也要验证实际会话是否被动态覆盖:
- 某些 ORM(如 Django、MyBatis)或连接池会在每次获取连接时显式执行
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - MySQL 8.0+ 全局变量
transaction_isolation和会话级变量可能不一致 - 查看真实执行语句的隔离上下文:
SELECT TRX_ID, TRX_ISOLATION_LEVEL FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
为什么你会“以为” RR 下有半一致性读
常见误判来源包括:
-
执行计划走的是全表扫描 + 行锁退化:RR 下若
WHERE条件未命中索引,InnoDB 会对所有扫描到的行加记录锁(甚至表锁),而 MySQL Server 层在判断 WHERE 不匹配后会调用unlock_row()主动释放不匹配行的锁——这个“先锁后放”的过程容易被误解为“返回旧版本再判断”,实则是锁粒度控制,与版本构造无关 -
混用了快照读和当前读:RR 下普通
SELECT是快照读,但UPDATE是当前读,两者看到的数据版本不同;如果应用逻辑中先查后更(且没加FOR UPDATE),第二次读可能因 MVCC 视图未刷新而“看起来像”读到了旧值 - 日志里出现 Lock wait timeout 或 semi-consistent 字样:某些 MySQL 版本(如 5.7.25)在慢日志中错误标记锁等待路径,或监控工具将 RC 场景误归因到 RR 连接
RR 下 UPDATE 真正的加锁行为
RR 隔离级别下,UPDATE 的锁策略取决于索引使用情况:
- 走主键或唯一索引:只对匹配行加 X 锁(记录锁)
- 走非唯一二级索引:对匹配的二级索引项加 X 锁,同时对对应的聚簇索引记录加 X 锁
- 无索引或索引失效:全表扫描 → 对每行尝试加 X 锁 → 若某行已被其他事务锁定,则当前事务直接阻塞等待(不是半一致性读,是真等)
- 范围条件(如
id > 10):除记录锁外,还会加 Next-Key Lock(记录锁 + 间隙锁),防止幻读
注意:RR 下没有“返回已提交版本交 Server 判断”这一步,InnoDB 直接把锁冲突抛给上层,由事务等待机制处理。
半一致性读是个很具体的优化机制,它只在 RC 下、扫描路径为全表或二级索引、且碰到已加 X 锁的行时才介入。RR 下你看到的“并发表现变好”或“锁等待减少”,往往来自别的因素:比如索引优化降低了扫描行数,或者应用层重试逻辑掩盖了阻塞。别让表象带偏对底层机制的理解。


















