Skip to content

写个加锁的 sql 解决你刚刚说的幻读?加的什么锁?

约 756 字大约 3 分钟

MySQL字节

2025-03-20

⭐ 题目日期:

字节 - 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 的工作流程

  1. 锁定现有记录:对 id=5 的行加行锁(Record Lock)。
  2. 锁定间隙
    • (3, 5):现有记录之间的间隙。
    • (5, +∞):最大记录后的间隙。
  3. 禁止插入:其他事务无法在 id > 3 的范围内插入新记录(如 id=4id=6)。

锁兼容性

  • 共享锁(S Lock):允许其他事务读,但禁止写。
  • 排他锁(X Lock):禁止其他事务读和写。
  • FOR UPDATE 会加 排他锁,完全禁止其他事务操作锁定范围。

5. 注意事项

  1. 索引要求
    • Next-Key Lock 依赖索引,若查询字段无索引,会退化为 表级锁,性能极差。
    • 示例中 id 是主键索引,因此锁范围精确。
  2. 性能影响
    • 范围锁可能导致高并发场景下的锁竞争,需权衡一致性和性能。
  3. 死锁风险
    • 多个事务按不同顺序加锁可能导致死锁,需合理设计事务逻辑。

6. 总结

操作加的锁解决的核心问题
SELECT ... FOR UPDATENext-Key Lock(行锁 + 间隙锁)幻读、不可重复读
SELECT ... LOCK IN SHARE MODE共享 Next-Key Lock允许读,禁止写

关键结论

  • 通过 SELECT ... FOR UPDATE 显式加锁,结合 Next-Key Lock 机制,可以彻底避免幻读。
  • 实际开发中需根据业务需求,在 一致性并发性能 之间找到平衡。