快照读是InnoDB在普通SELECT时基于MVCC和事务启动时刻ReadView读取一致性视图,不查最新数据;当前读(如SELECT...FOR UPDATE、UPDATE等)则读最新行并加锁。

什么是快照读:普通 SELECT 为什么“看不见”别人刚提交的数据?
快照读就是你敲下 SELECT * FROM t_user WHERE id = 1 时,InnoDB 默认干的事——它不查磁盘上最新的行,而是查你事务启动那一刻“拍下的快照”。这个快照由 MVCC 的 ReadView 决定,里面记着当时哪些事务还活着、哪些已提交。
常见错误现象:
事务 A 提交了 UPDATE t_user SET name = 'Alice' WHERE id = 1,事务 B 在同一隔离级别下执行普通 SELECT 却还是看到旧名字——不是 MySQL 没更新,是它压根没打算给你看最新版。
- REPEATABLE READ(默认)下:事务内所有快照读复用同一个
ReadView,所以第一次读完,后续再读也一样,哪怕别人早提交了 - READ COMMITTED 下:每次
SELECT都生成新ReadView,能看见其他事务已提交的修改,但仅限“这次查询前已提交”的 - 快照读不加任何锁,所以不会阻塞别人写,自己也不会被写阻塞——这是高并发读场景的底气
SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE 到底在锁什么?
这两个不是“增强版 SELECT”,而是当前读的入口——它们强制跳过快照,直奔最新数据,并立刻加锁。区别不在“读”,而在“锁的类型和意图”。
使用场景:
你要扣库存、要校验余额、要防止两条并发请求同时通过判断逻辑……这些地方不能靠“历史快照”,必须确认“此刻这条记录真实可用”。
-
SELECT ... FOR UPDATE加的是 X 锁(排他锁):别人既不能改,也不能用FOR UPDATE或LOCK IN SHARE MODE去读它 -
SELECT ... LOCK IN SHARE MODE加的是 S 锁(共享锁):别人还能加 S 锁一起读,但谁都不能加 X 锁去改 - 注意:即使
WHERE条件没命中任何行,InnoDB 在 RR 隔离级别下仍可能加间隙锁(Gap Lock),防止幻读——这点极易被忽略
为什么 UPDATE/DELETE 也属于当前读?
因为它们内部必然包含一次“读取目标行”的动作,而这个读,必须是当前读——否则就可能出现“读到旧版本→按旧值计算→覆盖写入”,导致丢失更新或逻辑错乱。
实操建议:
别以为只有显式加锁才算当前读。只要语句里有 UPDATE、DELETE、INSERT ... ON DUPLICATE KEY UPDATE,InnoDB 就会先对匹配的每一行做当前读(加 X 锁),再执行变更。
- 如果
WHERE走了非唯一索引,可能锁住不止一行,甚至锁住范围(Next-Key Lock) - 没加
WHERE条件的UPDATE会全表扫描+逐行加锁,风险极高,线上严禁 -
INSERT也会当前读:为检查唯一约束(如主键、UNIQUE 索引),它需要确认“这条记录现在到底存不存在”
RR 隔离级别下,快照读真的能防幻读吗?
能,但只在纯快照读场景下成立。一旦混入当前读,幻读就回来了——而且更隐蔽。
典型坑:
事务 A 先 SELECT * FROM orders WHERE status = 'pending'(快照读,看到 3 条);事务 B 插入一条新 pending 订单并提交;事务 A 接着 SELECT ... FOR UPDATE WHERE status = 'pending'——这时它会看到 4 条,并对这 4 条都加锁。新增那条就是幻行。
- MVCC 的快照读能避免“读出幻行”,但无法阻止别人插入后被当前读捕获
- InnoDB 在 RR 下用 Next-Key Lock(行锁 + 间隙锁)来堵住插入,但这只在当前读路径生效
- 如果你的业务逻辑里“先查后更”用了混合读法(比如查用普通 SELECT,改用 FOR UPDATE),幻读风险实际存在,得靠应用层加分布式锁或重试机制兜底
真正容易被忽略的点:快照读的“一致性”完全依赖事务首次执行快照读的时间点。之后无论等多久、别人提交多少次,只要没做当前读,你就永远卡在那个时间切片里——这不是 bug,是设计,但也是很多“数据不一致”问题的源头。










