MySQL 中的快照读与当前读:一场并发江湖的武林较量
在 MySQL 的江湖里,有两位著名的“读”法门——快照读(Snapshot Read) 与 当前读(Current Read) ,一个主打“隐身术”,一个擅长“当面打”。他们虽同为读取数据,却走着完全不同的修炼路线。
今天我们就来围观这两位大侠是如何在事务高并发的武林中各显神通的。
tips:相对应的专业版本请看上篇文章:juejin.cn/post/749876…
🎯 开篇三大心法(你得背下来)
✅ 当前读就像“看到别人刚刚擦干净的桌子”——总是读到最新别人已提交的数据。
✅ 幻读是数据库界的“灵异事件”,只有当前读才能用锁链把幽灵锁住。
✅
LOCK IN SHARE MODE就像温文尔雅的绅士:“我不改,但你们也别想偷偷动!”
一、概念篇:你是谁?你从哪来?
🥷 快照读(Snapshot Read)
- 修炼路线:MVCC(多版本并发控制)
- 绝招:我读的是我刚进门那一刻看到的数据,别人后来改了?无感!
- 特点:轻功好,不加锁,速度快,但容易被幻觉迷惑
SELECT * FROM product WHERE id = 1;
🥷 当前读(Current Read)
- 修炼路线:加锁+最新视角
- 绝招:你刚改完数据?别动,我现在就锁住它!
- 特点:数据最新 + 加锁防抢改,特别适合守株待兔、查完就改的操作。
SELECT * FROM product WHERE id = 1 FOR UPDATE;
二、实战分类表:谁属于谁门派?
| SQL 语句 | 门派 | 是否加锁 | 一句话解释 |
|---|---|---|---|
SELECT ... | 快照读 | ❌ | 无锁轻功,高并发利器 |
SELECT ... FOR UPDATE | 当前读 | ✅(X锁) | 我锁住你了,别动! |
SELECT ... LOCK IN SHARE MODE | 当前读 | ✅(S锁) | 我不改你,但你也别改我 |
UPDATE / DELETE | 当前读 | ✅(X锁) | 改之前都得看一眼,顺手加个锁 |
INSERT ... ON DUPLICATE KEY | 当前读 | ✅ | 你别抢,看看我先来的没 |
三、对比擂台:快照读 vs 当前读
| 比武项目 | 快照读 | 当前读 |
|---|---|---|
| 数据视角 | 事务开始时快照 | 最新别人提交的数据 |
| 是否加锁 | ❌ | ✅ |
| 并发表现 | 非常优秀 | 稍逊一筹(但安全) |
| 能否防幻觉 | 表面上能(靠视角) | 真正能(靠锁) |
| 上场场景 | 报表查询、前台展示 | 秒杀、库存扣减、查完就改 |
四、幻读:数据库里的灵异事件
你说:“我刚才查了一遍,下一秒怎么多了一行数据?” 这不是你眼花了,是幻读(Phantom Read) !
😵 幻读长这样:
一个事务中你执行
SELECT * FROM order WHERE amount > 100,查完你以为就这些了。 结果另一个事务偷偷插入了一个 amount = 999 的订单。 你再查一次,哎?数据变了!
🧘♂️ 快照读的应对:
“我不看我不看,幻觉看不见”,快照读通过视图版本避免感知新增数据。
🧙♀️ 当前读的应对:
“来人,把这段范围都锁住!不许乱动!”——靠间隙锁 + 行锁,当前读真·杜绝幻读。
五、实战演练:两位事务武林高手过招
-- T1:快照读派掌门人
START TRANSACTION;
SELECT * FROM product WHERE id = 1; -- 看到了 Alice
-- T2:新晋事务,更新数据
START TRANSACTION;
UPDATE product SET name = 'Bob' WHERE id = 1;
COMMIT;
-- T1 又查一次
SELECT * FROM product WHERE id = 1; -- 还是看到 Alice,时间静止了
-- 如果 T1 换成当前读呢?
SELECT * FROM product WHERE id = 1 FOR UPDATE; -- 立刻看到 Bob,并锁住!
六、开发秘籍:什么场景用啥武功
| 你要干啥? | 推荐用法 |
|---|---|
| 查询列表、前台浏览、报表 | 快照读,轻功第一 |
| 查库存、查了还要改 | 当前读,锁得住敌人 |
| 防止抢单、秒杀冲突 | 当前读(FOR UPDATE) |
| 验证数据是否存在再插入 | 当前读 or S锁 |
七、LOCK IN SHARE MODE:斯文派中的守门大将
你说:“我不想改数据,我只想读一下,但别人千万别改!”
这时候你不想拿大刀 (FOR UPDATE),但也得带把剑。
于是——LOCK IN SHARE MODE 出场:
- 自己读没问题
- 别人也能读
- 但别人不能改(否则阻塞)
适合啥?比如你判断某人是否注册,或者你验证某条数据是否存在再插入。
八、Spring Boot 实战
@Service
public class ProductService {
@Transactional
public void purchase(Long productId) {
Product product = productMapper.selectForUpdate(productId);
if (product.getStock() <= 0) {
throw new RuntimeException("库存不足");
}
product.setStock(product.getStock() - 1);
productMapper.updateById(product);
}
}
@Select("SELECT * FROM product WHERE id = #{id} FOR UPDATE")
Product selectForUpdate(Long id);
锁,锁住了,改,改下了,事儿就成了。
九、常见江湖误解
| 流言 | 实情 |
|---|---|
| FOR UPDATE 也是快照读? | ❌ 人家是当前读,锁得严! |
| 快照读能看到别人新改的? | ❌ MVCC 就是“视而不见” |
| LOCK IN SHARE MODE 不加锁? | ❌ 加了共享锁呢! |
| 插入操作永不被锁? | ❌ 冲突了照样等着排队 |
🔚 收功:学武之道,知止而后有得
在数据库武林中:
- 快照读走的是轻功路线,查询快,但幻觉缠身;
- 当前读是硬派功夫,锁得住、改得准,代价就是慢点;
- 选对武功,方可战无不胜。
打得赢不是最厉害的,打得准,锁得巧,才是高手。