快照读彻底绕过锁子系统,不申请任何锁(含意向锁),仅通过readview与undo log版本链判断可见性;当前读则必须定位最新物理行并强制加锁,加锁行为与隔离级别无关,仅取决于语句类型和索引条件。

快照读根本不走锁子系统,连意向锁都不申请
快照读不是“加了读锁但没阻塞”,而是彻底绕过锁路径:InnoDB 在执行 SELECT * FROM t WHERE id = 1 这类语句时,压根不调用锁管理器,不申请任何锁——包括行锁、间隙锁、临键锁,甚至表级意向锁(IX 或 IS)都不会出现。
它只做两件事:查当前事务的 ReadView,再顺着记录的 DB_ROLL_PTR 往前遍历 undo log 版本链,比对每条版本的 DB_TRX_ID 是否可见。整个过程完全无锁竞争,所以:
- 事务 A 快照读时,事务 B 正在对同一行执行
UPDATE并持有 X 锁,A 不会等待,直接返回自己可见的历史版本 - 事务 A 的快照读也不会让事务 C 的
INSERT卡在插入意向锁上——因为 A 根本没占任何锁资源 - 即使全表被其他事务用
SELECT ... FOR UPDATE锁死,快照读仍能秒出结果
当前读必须定位最新物理行并立即加锁
只要触发当前读,InnoDB 就放弃 MVCC 版本链,直接跳到聚簇索引页找最新已提交的那条记录,然后按语义加锁——这不是可选项,是执行路径强制要求。
常见当前读场景和对应行为:
-
SELECT ... FOR UPDATE→ 定位行后加 X 锁,若条件无索引,会全表扫描+全区间加 next-key lock -
UPDATE ... WHERE id = 1→ 先当前读取 id=1 的最新行(隐式当前读),加 X 锁,再修改 -
INSERT ... ON DUPLICATE KEY UPDATE→ 对冲突的唯一键行做当前读并加 X 锁;新插入行只加插入意向锁,但 X 锁和插入意向锁共存极易引发死锁
注意:RC 和 RR 隔离级别下,当前读的加锁行为完全一致。差异只存在于快照读的 ReadView 生成策略,和锁无关。
隔离级别只影响 ReadView 复用,不影响加锁逻辑
很多人误以为 RR 级别“锁得更严”,其实是个经典误解:当前读在 RC 和 RR 下加什么锁、锁多大范围,完全由语句类型和索引情况决定,和隔离级别无关。
真正被隔离级别控制的,只有快照读的 ReadView 生命周期:
-
RC:每次SELECT都新建ReadView,所以快照读能看到其他事务刚提交的新值 -
RR:事务第一次快照读后,后续所有SELECT都复用同一个ReadView,导致“读不到新提交数据”——但这和锁毫无关系,只是版本过滤规则变了
一个关键副作用:RR 下长事务不提交,会让它的 ReadView 一直有效,undo log 无法 purge,版本链膨胀,快照读性能会随时间明显下降。
快照读不加锁,但强依赖 undo log 存活
这是最容易被忽略的临界点:快照读虽不申请锁,却极度依赖 undo log 中的历史版本可用。如果事务长期不提交,它的 ReadView 会把大量旧版本标记为“仍可见”,导致:
- purge 线程无法回收这些 undo log,
undo tablespace持续增长,可能撑爆磁盘 - 每次快照读要遍历更长的版本链,CPU 和 IO 开销上升
- 极端情况下,undo log 膨胀到几十 GB,而 DBA 查监控才发现问题——此时锁本身早不是瓶颈了
所以线上排查慢查询,不能只盯着 SHOW ENGINE INNODB STATUS 里的锁信息,还得看 INFORMATION_SCHEMA.INNODB_METRICS 中的 undo_log_truncated 和 history_list_length 指标。











