当前读与快照读深度解析
候选人小李在字节三面中,面试官问:
"SELECT * FROM orders WHERE id = 1 和 SELECT * FROM orders WHERE id = 1 FOR UPDATE 有什么区别?"
小李说:"FOR UPDATE 会加锁。"
面试官追问:"加了什么锁?锁住后,其他事务还能读取这条数据吗?"
小李说:"应该不能...吧?"
面试官继续追问:"如果用快照读呢?其他事务能读到吗?"
小李彻底懵了。
【面试官心理】 这道题我用来区分"知道加锁"和"理解锁机制"的候选人。能说出 FOR UPDATE 加排他锁的占 60%,能区分快照读和当前读的占 30%,能说清 MVCC 和锁如何配合的占 10%。当前读和快照读是 MySQL 并发控制的核心概念。
一、两种读的本质区别 🔴
1.1 快照读(Snapshot Read)
快照读读取的是历史版本的数据,不加锁。
1.2 当前读(Current Read)
当前读读取的是最新版本的数据,并且加锁。
1.3 对比表
【面试官心理】 我问他快照读和当前读的区别,很多候选人会说"一个是读一个是写"。但能讲清楚 MVCC 保护快照读、锁保护当前读的候选人,基本都理解 MySQL 的并发控制机制。
二、MVCC 如何实现快照读 🔴
2.1 快照读的完整流程
2.2 为什么快照读不加锁
快照读通过 ReadView 读取历史版本,不需要对数据行加锁,因为:
- 其他事务修改数据会创建新版本,不影响历史版本
- 历史版本通过 undo log 链维护,有 MVCC 机制保护
- 读取历史版本不会阻塞写操作
三、锁如何实现当前读 🔴
3.1 当前读必须加锁的原因
3.2 为什么当前读需要读取最新版本
如果 UPDATE 语句不先加锁,直接 UPDATE 时 MySQL 也会加锁。但先 SELECT FOR UPDATE 再 UPDATE 是更安全的做法,可以避免竞态条件。
四、两者的组合使用 🟡
4.1 典型的事务模式
4.2 一致性读和一致性写的配合
五、RR 级别下的幻读问题 🟡
5.1 快照读幻读:MVCC 解决
5.2 当前读幻读:Next-Key Lock 解决
【面试官心理】 能区分快照读幻读和当前读幻读,并说明它们分别由什么机制解决的候选人,基本都是 P7 水准。RR 级别下 MySQL 的幻读解决是一个复杂的机制。
六、生产避坑 🟡
6.1 快照读误用导致数据不一致
问题:快照读看到的 balance 可能是历史版本,导致重复扣款。
解决方案:使用 @Modifying 注解或原生 SQL 的 SELECT ... FOR UPDATE。
6.2 当前读导致的死锁
解决死锁的方法:按固定顺序加锁。所有事务都先锁定 id 小的记录,再锁定 id 大的记录。
七、面试追问链 🟡
第一层:快照读和当前读的区别是什么?
- 候选人:快照读不加锁读历史版本,当前读加锁读最新版本
第二层:MVCC 保护的是哪种读?
- 候选人:快照读
第三层:RR 级别下,快照读能防止幻读吗?
- 候选人:能,通过 ReadView 读取历史版本
第四层:如果既要快照读的一致性,又要防止幻读,怎么办?
- 候选人:在同一个事务中混合使用快照读和当前读,或使用 SERIALIZABLE 级别