mysql快照读仅在repeatable read隔离级别下默认生效,此时事务首次select生成readview并全程复用;read committed每次select新建readview,read uncommitted和serializable则不使用快照读。

快照读在什么隔离级别下生效
MySQL 的快照读(Snapshot Read)只在 REPEATABLE READ 隔离级别下默认启用,且依赖 MVCC(多版本并发控制)。在 READ COMMITTED 下虽然也用 MVCC,但每次 SELECT 都会新建一致性视图(read view),不复用事务开始时的快照;而 REPEATABLE READ 会在事务第一次执行 SELECT 时创建 read view,并在整个事务中复用——这才是“真正意义”的快照读。
注意:READ UNCOMMITTED 和 SERIALIZABLE 下快照读不生效:READ UNCOMMITTED 直接读最新行版本(可能脏读),SERIALIZABLE 会对所有 SELECT 加 LOCK IN SHARE MODE 级别的锁,退化为当前读。
哪些语句触发快照读,哪些会绕过
快照读仅适用于纯 SELECT(不含锁提示),例如:
-
SELECT * FROM t WHERE id = 1→ 快照读(无锁、不阻塞、不被阻塞) -
SELECT * FROM t WHERE id > 100→ 快照读(范围查询也适用)
以下语句**强制当前读**,会加锁并可能触发锁等待,快照读失效:
SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE-
UPDATE、DELETE、INSERT(即使带WHERE条件匹配不到行,也可能加 gap lock) -
SELECT语句显式加了FOR UPDATE或LOCK IN SHARE MODE提示
常见误区:以为“只要没写 FOR UPDATE 就一定不锁”,其实 UPDATE t SET x=1 WHERE id=1 即使 id=1 行不存在,InnoDB 仍可能在索引间隙上加 INSERT intention lock,进而与其它事务冲突。
为什么有时快照读还是卡住?检查这三处
快照读本身不加锁,理论上永不等待。但如果观察到 SELECT 被阻塞,基本可断定它**没走快照读**,而是被意外转成当前读或遭遇元数据锁(MDL)。排查重点:
- 确认事务隔离级别:执行
SELECT @@transaction_isolation,不是REPEATABLE-READ就别指望稳定快照 - 检查是否隐式升级为当前读:比如该表上有
SELECT正在执行FOR UPDATE,同时另一个会话执行ALTER TABLE,此时后续所有SELECT可能因等待 MDL 锁而挂起(和 MVCC 无关) - 确认没有触发只读事务自动升级:MySQL 8.0+ 中,若只读事务里执行了
SELECT后又执行UPDATE,后续SELECT可能复用更新后的 read view,行为变复杂
验证方式:开启 performance_schema,查 data_locks 表,看阻塞的 SELECT 是否出现在锁记录中——如果出现,说明它根本没走快照路径。
快照读不是万能的:它解决不了的三类问题
快照读只解决“读不阻塞写、写不阻塞读”的并发问题,但对以下场景无效:
- 写-写冲突:两个事务同时
UPDATE同一行,后提交者会因行锁等待或死锁检测失败而回滚 - 幻读现象本身未消除:快照读能避免“同一事务中两次
SELECT看到新插入的行”,但无法阻止其他事务在你事务执行期间INSERT并提交——只是你“看不见”而已;如果你要基于当前结果做决策(如“没查到就插入”),仍需配合SELECT ... FOR UPDATE+ 唯一约束来防止重复 - 长事务导致的 undo log 膨胀:快照依赖 undo log 保留旧版本,若一个事务长达数小时,会阻止 purge 线程清理旧版本,拖慢整个实例性能
真正需要“避免锁等待”的业务逻辑,不能只依赖快照读,得结合应用层重试、更细粒度拆分事务、或改用乐观锁(如带 version 字段的 UPDATE ... WHERE version = ?)。











