RC下UPDATE读两次是为了避免锁等待放大并确保WHERE判断基于已提交数据:第一次读旧版本判断条件,不加锁;若匹配,第二次才当前读并加X锁。该机制仅在RC隔离级别、目标行被X/S锁占用时生效。

RC隔离级别下,UPDATE语句遇到已加锁行时,会触发Semi-Consistent Read机制——它不是跳过锁,而是“先读旧版本判断条件,再决定是否加锁重读”。
为什么RC下UPDATE要读两次?
在READ COMMITTED隔离级别中,UPDATE不能像REPEATABLE READ那样依赖事务开始时的快照。当它扫描到一行已被其他事务加锁(比如被SELECT ... FOR UPDATE或另一个UPDATE锁住),InnoDB不会直接等待,而是返回该行**最近已提交的版本**给MySQL Server层;Server层用这个版本去判断WHERE条件是否匹配。若匹配,才发起第二次读(此时是Current Read),并尝试获取行锁(X锁)或等待锁释放。
这个“两次读”设计,本质是为了避免在RC下因盲目加锁导致的锁等待放大,同时保证WHERE判断基于已提交数据——否则就退化成脏读了。
Semi-Consistent Read只在哪些场景生效?
它仅对UPDATE和DELETE语句有效,且必须同时满足:
- 当前会话隔离级别为
READ COMMITTED(SELECT @@transaction_isolation返回READ-COMMITTED) - 目标行已被其他事务以
X或S锁占用(如被UPDATE、SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE锁定) - 未启用
innodb_locks_unsafe_for_binlog=ON(该参数已从MySQL 8.0起移除,仅影响5.6/5.7)
注意:INSERT ... ON DUPLICATE KEY UPDATE中的UPDATE分支也会触发;但纯SELECT、INSERT或REPLACE不参与此机制。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
和普通Current Read的区别在哪?
关键差异在于“第一次读是否加锁”和“可见性依据”:
- 普通
Current Read(如SELECT ... FOR UPDATE):直接读最新版本 + 立即加锁,不看是否已提交 - Semi-Consistent Read的第一读:返回的是**已提交的快照版本**,不加锁,也不阻塞;仅用于条件评估
- 第二次读(条件命中后):才是真正的
Current Read,读最新版本并尝试加X锁
这意味着,在高并发更新同一行的场景下,Semi-Consistent Read能减少不必要的锁等待——例如多个UPDATE ... WHERE id = 1并发执行时,只有第一个真正抢到锁的会走完两阶段,其余可能快速判断“条件不满足”而跳过(如果旧版本不满足WHERE),从而降低锁冲突概率。
容易被忽略的边界行为
这个机制有几个隐性约束,不注意会导致预期外的结果:
- 它不改变
UPDATE的原子性:即使第一次读到旧版本满足条件,第二次读时若该行已被其他事务提交修改,仍可能发现新值不满足WHERE,最终UPDATE影响行为为0行(ROW_COUNT()返回0) - 它不规避幻读:Semi-Consistent Read只作用于“已存在且被锁的行”,对新插入的满足
WHERE条件的行无效;RC本就不解决幻读 - 它依赖聚簇索引定位:只有通过主键或唯一索引等精确查找路径访问单行时,才能高效返回旧版本;范围扫描(如
WHERE c > 5)中若某行被锁,仍会触发,但性能开销更明显
最易踩的坑是:误以为“RC下UPDATE总能读到最新提交值”,其实它只在锁冲突时才退回到旧版本做判断;无锁情况下,仍是直接Current Read取最新值并加锁。










