加了 FOR UPDATE 就变成当前读,因为显式加锁需基于最新行数据判断锁类型(记录锁/间隙锁/临键锁),快照读无法反映实时锁状态和值,故强制降级为当前读。

为什么加了 FOR UPDATE 就变成当前读
因为 FOR UPDATE 是显式加锁语句,MySQL 必须读取最新版本的行数据才能判断是否能加锁、加什么锁(记录锁?间隙锁?临键锁?)。快照读看到的是历史版本,无法反映当前行的锁状态和实际值,自然无法完成加锁动作——所以直接退化为当前读。
REPEATABLE READ 下普通 SELECT 是快照读,但一写就变当前读
根本原因在于 MVCC 的设计分工:快照读只负责“读一致”,不参与并发控制;而任何写操作(UPDATE、DELETE、INSERT ... ON DUPLICATE KEY UPDATE)或带锁读(SELECT ... LOCK IN SHARE MODE)都必须基于最新数据做冲突判断和锁分配。InnoDB 不会用快照版本去更新或加锁,否则可能覆盖别人刚提交的修改,破坏原子性。
- 即使在
REPEATABLE READ下,UPDATE t SET x = x + 1 WHERE id = 100也必须先查出id = 100当前行的最新值和事务 ID,再决定是否加锁、是否触发唯一约束检查 - 如果用快照版本去更新,就可能出现“基于过期值计算后覆盖了新提交的数据”这种逻辑错误
- 所以 InnoDB 在执行写前会自动触发一次当前读,拿到最新行并加 X 锁
隔离级别本身不决定读类型,语义需求才决定
很多人误以为 “REPEATABLE READ 就是快照读,READ COMMITTED 就是当前读”,其实不对。真正起作用的是操作意图:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
-
SELECT无锁 → 默认走快照读(取决于隔离级别生成 ReadView 的时机) -
SELECT ... FOR UPDATE/UPDATE/DELETE→ 强制当前读,跟隔离级别无关 - 哪怕在
READ UNCOMMITTED下,UPDATE仍需当前读——因为要加锁,不能基于未提交的脏数据去更新
换句话说,隔离级别只管“你能看到哪些已提交的版本”,而“要不要加锁、基于哪一行加锁”,由 SQL 类型和引擎的并发控制逻辑决定。
容易被忽略的关键点:快照读和当前读共存于同一事务中
一个事务里可以混合使用两种读:前面用普通 SELECT 看快照,后面用 SELECT ... FOR UPDATE 查最新行并加锁。这时候你得清楚——两次读看到的数据可能不同,尤其在 READ COMMITTED 下,第二次当前读看到的可能是其他事务刚提交的新值,而第一次快照读还停留在旧状态。这不是 bug,是设计使然,但业务逻辑必须对此有预期。

















