外观
写个加锁的 sql 解决你刚刚说的幻读?加的什么锁?
⭐ 题目日期:
字节 - 2024/12/17
📝 题解:
在 MySQL 的可重复读(Repeatable Read)隔离级别下,可以通过显式加锁解决幻读问题。以下是具体的加锁 SQL 和锁类型说明:
1. 解决幻读的加锁 SQL
-- 在事务中执行以下查询,显式加锁
SELECT * FROM users WHERE id > 3 FOR UPDATE;
- 作用:锁定满足
id > 3
的 现有记录 和 间隙,禁止其他事务插入或修改该范围内的数据。 - 适用场景:对需要严格避免幻读的范围查询(如统计、对账等操作)。
2. 加的锁类型
MySQL 通过 Next-Key Locks 实现范围锁定,具体包含两种锁:
锁类型 | 作用范围 | 示例(id > 3 ) |
---|---|---|
行锁(Record Lock) | 锁定已存在的记录 | 锁定 id=5 的记录 |
间隙锁(Gap Lock) | 锁定索引范围之间的间隙 | 锁定 (3, 5) 和 (5, +∞) 的间隙 |
Next-Key Lock = 行锁 + 间隙锁,覆盖整个查询范围。
3. 验证锁效果
事务 A(加锁方)
BEGIN;
-- 显式加锁,锁定 id > 3 的范围
SELECT * FROM users WHERE id > 3 FOR UPDATE;
-- 此时其他事务无法在 id > 3 的范围内插入新数据
COMMIT;
事务 B(尝试插入)
BEGIN;
-- 尝试插入 id=6 的记录,会被阻塞直到事务 A 提交
INSERT INTO users (id, name) VALUES (6, 'F');
COMMIT;
结果:事务 B 的插入操作被阻塞,直到事务 A 提交后才执行,避免了幻读。
4. 锁的底层原理
Next-Key Lock 的工作流程
- 锁定现有记录:对
id=5
的行加行锁(Record Lock)。 - 锁定间隙:
(3, 5)
:现有记录之间的间隙。(5, +∞)
:最大记录后的间隙。
- 禁止插入:其他事务无法在
id > 3
的范围内插入新记录(如id=4
或id=6
)。
锁兼容性
- 共享锁(S Lock):允许其他事务读,但禁止写。
- 排他锁(X Lock):禁止其他事务读和写。
FOR UPDATE
会加 排他锁,完全禁止其他事务操作锁定范围。
5. 注意事项
- 索引要求:
- Next-Key Lock 依赖索引,若查询字段无索引,会退化为 表级锁,性能极差。
- 示例中
id
是主键索引,因此锁范围精确。
- 性能影响:
- 范围锁可能导致高并发场景下的锁竞争,需权衡一致性和性能。
- 死锁风险:
- 多个事务按不同顺序加锁可能导致死锁,需合理设计事务逻辑。
6. 总结
操作 | 加的锁 | 解决的核心问题 |
---|---|---|
SELECT ... FOR UPDATE | Next-Key Lock(行锁 + 间隙锁) | 幻读、不可重复读 |
SELECT ... LOCK IN SHARE MODE | 共享 Next-Key Lock | 允许读,禁止写 |
关键结论:
- 通过
SELECT ... FOR UPDATE
显式加锁,结合 Next-Key Lock 机制,可以彻底避免幻读。 - 实际开发中需根据业务需求,在 一致性 和 并发性能 之间找到平衡。