一、快照读(Snapshot Read)与当前读(Current Read)
1.1 快照读(Snapshot Read)
快照读是指事务在执行 SELECT 查询时,读取的是事务启动时的数据快照。它不会阻塞其他写操作,也不会被其他写操作阻塞。这是因为快照读依赖于 MVCC 机制,通过读取历史版本来实现一致性视图。
特点:
- 无锁:快照读不需要加任何锁。
- 一致性视图:读取的是事务开始时的数据快照。
- 适用场景:适用于大多数只读查询场景。
示例:
sql
SELECT * FROM users WHERE id = 1;
1.2 当前读(Current Read)
当前读是指事务在执行某些特定查询时,需要获取最新的数据视图,因此会加锁以确保数据的一致性。
常见的当前读操作包括 SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE。
特点:
- 加锁:当前读需要加锁,防止其他事务修改数据。
- 最新数据:读取的是当前时刻的最新数据。
- 适用场景:适用于需要保证数据一致性的写操作或高并发场景下的读操作。
示例:
sql
SELECT * FROM users WHERE id = 1 FOR UPDATE;
核心区别概览
| 特性 | SELECT ... FOR UPDATE |
SELECT ... LOCK IN SHARE MODE |
|---|---|---|
| 中文名 | 排他锁(写锁) | 共享锁(读锁) |
| 锁类型 | X 锁(Exclusive Lock) | S 锁(Shared Lock) |
| 是否阻塞其他事务读? | ❌ 不阻塞普通 SELECT(快照读) ✅ 阻塞其他 FOR UPDATE / LOCK IN SHARE MODE |
❌ 不阻塞普通 SELECT ❌ 不阻塞其他 LOCK IN SHARE MODE ✅ 阻塞 FOR UPDATE |
| 是否阻塞其他事务写? | ✅ 阻塞所有写操作(UPDATE/DELETE) | ✅ 阻塞写操作(因为写需要 X 锁) |
| 典型用途 | "我要修改这条数据,请别人别动" | "我要确保这条数据不被改,但允许多人同时读" |
| 隔离级别要求 | 所有隔离级别均生效 | 所有隔离级别均生效 |
一句话总结:
FOR UPDATE= "我独占,谁也别碰" (连"读锁"都不让别人加)LOCK IN SHARE MODE= "你可以读,但不能改"(允许别人加同样的"读锁")
锁机制详解
1. SELECT ... FOR UPDATE(排他锁 / X 锁)
- 对查询命中的每一行 加 X 锁(排他锁)
- X 锁特性 :
- 与其他任何锁(S 或 X)互斥
- 同一时刻只能有一个事务持有某行的 X 锁
- 效果 :
- 其他事务无法对该行执行
UPDATE、DELETE、FOR UPDATE、LOCK IN SHARE MODE - 但普通
SELECT(快照读)仍可执行(因为不走当前读,不受锁影响)
- 其他事务无法对该行执行
示例:
sql
-- 事务 A
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 加 X 锁
-- 此时事务 B 执行以下语句会阻塞:
-- UPDATE accounts SET balance = 100 WHERE id = 1;
-- SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE;
-- 但以下语句**不会阻塞**:
-- SELECT * FROM accounts WHERE id = 1; -- 快照读,走 MVCC
2. SELECT ... LOCK IN SHARE MODE(共享锁 / S 锁)
- 对查询命中的每一行 加 S 锁(共享锁)
- S 锁特性 :
- 与 S 锁兼容(多个事务可同时持有)
- 与 X 锁互斥
- 效果 :
- 其他事务可以执行
LOCK IN SHARE MODE(并发读) - 但无法执行
FOR UPDATE、UPDATE、DELETE(因为这些需要 X 锁)
- 其他事务可以执行
示例:
sql
-- 事务 A
BEGIN;
SELECT * FROM products WHERE id = 100 LOCK IN SHARE MODE; -- 加 S 锁
-- 事务 B 可以:
SELECT * FROM products WHERE id = 100 LOCK IN SHARE MODE; -- 成功(S-S 兼容)
-- 事务 B 不能:
SELECT * FROM products WHERE id = 100 FOR UPDATE; -- 阻塞(S-X 冲突)
UPDATE products SET price = 99 WHERE id = 100; -- 阻塞(需要 X 锁)
关键注意事项
- 两者都属于"当前读",会绕过 MVCC
- 它们读取的是最新已提交的数据(不是快照)
- 即使在
REPEATABLE READ隔离级别下,也会看到其他事务已提交的最新值
- 锁的范围取决于 WHERE 条件和索引
- 如果
WHERE条件能命中索引 → 只锁命中的行 - 如果全表扫描 → 锁住所有扫描过的行(甚至间隙锁!)
- 在
REPEATABLE READ下,还会加 Next-Key Lock(行锁 + 间隙锁) 防止幻读
示例(RR 隔离级别):
sql
-- 假设 id 是主键
SELECT * FROM t WHERE id = 5 FOR UPDATE;
-- 只锁 id=5 这一行
-- 假设 name 无索引
SELECT * FROM t WHERE name = 'Alice' FOR UPDATE;
-- 可能锁住整个表(或大量无关行)!
MySQL 8.0+ 已废弃 LOCK IN SHARE MODE
-
官方推荐使用
FOR SHARE替代(语法更清晰):sqlSELECT * FROM t WHERE id = 1 FOR SHARE; -- 等价于 LOCK IN SHARE MODE -
但功能完全一致,旧语法仍支持。
使用场景对比
SELECT ... FOR UPDATE 适用场景
-
资金转账 :确保账户余额在读取后不被他人修改
sqlBEGIN; SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE; -- 检查余额足够 UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; COMMIT; -
库存扣减:防止超卖
-
状态机更新:如订单状态变更
SELECT ... LOCK IN SHARE MODE 适用场景
-
报表生成 :确保统计期间数据不被修改
sqlBEGIN; SELECT SUM(amount) FROM orders WHERE status = 'paid' LOCK IN SHARE MODE; -- 生成报表(其他人可读,但不能改订单状态) COMMIT; -
父子数据一致性校验:如检查订单与商品是否存在
但注意 :由于 S 锁容易引发死锁(多个事务互相持有 S 锁,再申请 X 锁),现代应用更倾向于用
FOR UPDATE+ 业务逻辑优化 ,而非LOCK IN SHARE MODE。
死锁风险对比
| 场景 | FOR UPDATE |
LOCK IN SHARE MODE |
|---|---|---|
| 死锁概率 | 中(典型:A→B, B→A) | 高(S 锁 + 后续 X 锁易形成环) |
| 典型案例 | 两个事务交叉更新两行 | 事务 A 持 S 锁想升级 X 锁,事务 B 也在等 |
建议 :除非明确需要"多人并发读+防写",否则优先使用
FOR UPDATE。
总结:如何选择?
| 你的需求 | 推荐语句 |
|---|---|
| "我要读完就改,别让别人干扰" | ✅ SELECT ... FOR UPDATE |
| "我只需要确保数据不被改,允许多人同时读" | ⚠️ SELECT ... FOR SHARE(谨慎使用) |
| "我只是查一下,不需要锁" | ✅ 普通 SELECT(走 MVCC 快照读) |
终极建议:
- 90% 的场景用
FOR UPDATE足够- 尽量避免
LOCK IN SHARE MODE,除非你清楚其并发模型- 务必使用索引,避免意外锁表
- 事务尽量短,减少锁持有时间
1.3 快照读 vs 当前读:对比表
| 特性 | 快照读(Snapshot Read) | 当前读(Current Read) |
|---|---|---|
| 加锁 | 无 | 是 |
| 数据一致性 | 事务启动时的快照 | 当前时刻的最新数据 |
| 并发性能 | 高 | 中等 |
| 常见操作 | SELECT |
SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE |
二、MVCC 仅适用于快照读,不适用于当前读
2.1 MVCC 的作用边界
MVCC 主要应用于快照读 ,通过为每个事务分配一个唯一的事务 ID(DB_TRX_ID),并维护多个版本的历史数据,使得不同事务可以读取到不同的数据版本。然而,MVCC 并不适用于当前读,因为当前读需要获取最新的数据视图,这通常涉及到加锁操作。
关键点:
- 快照读:使用 MVCC 提供一致性视图。
- 当前读:绕过 MVCC,直接访问最新数据并加锁。
三、总结
MVCC 是 MySQL InnoDB 实现高并发读写的基石,通过快照读提供了无锁的一致性视图,极大地提升了系统的并发性能。
然而,MVCC 并不适用于所有类型的读操作,特别是那些需要获取最新数据的当前读操作。
关键知识点:
- 快照读:无锁,读取事务启动时的数据快照。
- 当前读:加锁,读取当前时刻的最新数据。
- MVCC:适用于快照读,不适用于当前读。
- Undo Log:记录历史版本,支持多版本存储和事务回滚。
相关命令与工具
-
查看活跃事务:
sqlSELECT trx_id, trx_state, trx_started FROM information_schema.innodb_trx ORDER BY trx_started DESC; -
查看锁定信息(MySQL 8.0+):
sqlSELECT * FROM performance_schema.data_locks;