题目请解释 MySQL InnoDB 中的间隙锁Gap Lock是什么以及它解决了什么问题。问题剖析先讲个小故事假设你开了一个水果店货架上有编号为 1、5、10 的三个苹果。保洁阿姨过来问你你家店里有编号 4 的苹果吗 你说没有。话音刚落供应商突然往货架上放了一个编号 4 的苹果。这下好了——你刚从嘴里说出去的话瞬间成了假话。这个问题在数据库里就叫幻读Phantom Read。而间隙锁就是 MySQL 解决幻读的武器。一、间隙锁到底锁了什么字面上已经很直白了——锁住的是间隙而不是记录。-- 假设 id 字段有这些值1, 5, 10, 15 SELECT * FROM product WHERE id BETWEEN 5 AND 10 FOR UPDATE;对于这条 SQLInnoDB 不仅锁住 id5 和 id10 这两条存在的记录还会锁住它们之间的空档——也就是 (5, 10) 这个区间。即便这个区间里没有实际数据你也别想往里面插东西。粗暴理解间隙锁锁的不是已经存在的行而是锁住了还不存在但可能被插入的行。二、为什么会冒出间隙锁这个东西这就不得不提数据库中一个经典的难题——幻读。什么是幻读用上面的例子说明事务 A事务 B查询 id 在 5~10 之间的数据发现 2 条插入 id7 的数据并提交再次查询 id 在 5~10 之间发现 3 条多了一条事务 A 在同一个事务中两次同样的查询返回了不同数量的行就像出现了幻觉——这就是幻读。行级锁Record Lock能锁住已经存在的记录但它管不了还不存在的记录插入。于是间隙锁诞生了。三、间隙锁的三种队友——认清 InnoDB 锁家族InnoDB 的行级锁实际上分三种锁类型锁住的对象一句话解释Record Lock记录锁索引上的某一条记录这条记录归我了谁也别动Gap Lock间隙锁索引记录之间的空隙这片空档我占了别想往里塞东西Next-Key Lock临键锁Record Lock Gap Lock 的组合这条记录和它前面的空隙我都包了用一张图直观感受一下。假设 id 索引有值1, 5, 10, 15数据 1 5 10 15 索引 ●——————○ ●——————○ ●——————○ ● ↑ 间隙 ↑ ↑ 间隙 ↑ ↑ 间隙 ↑ (-∞,1) (1,5) (5,10) (10,15)Record Lock锁的是 ●存在的记录Gap Lock锁的是 ○间隙即不存在的空间Next-Key Lock锁的是 ○●一个区间 右端点有意思的是Next-Key Lock 才是 InnoDB 的默认行级锁策略不是单纯的 Record Lock。四、代码验证——怎么亲眼看到间隙锁光说不练确实有点虚。我们来实战一下-- 准备数据 CREATE TABLE product ( id INT PRIMARY KEY, name VARCHAR(50) ) ENGINEInnoDB; INSERT INTO product VALUES (1, 苹果), (5, 香蕉), (10, 橘子), (15, 葡萄);会话 A开启事务对 id10 的记录上锁BEGIN; SELECT * FROM product WHERE id 10 FOR UPDATE; -- 此时会给 id10 加 Record Lock -- 同时给 (5,10) 和 (10,15) 加 Gap Lock会话 B尝试往间隙里插数据BEGIN; INSERT INTO product VALUES (7, 西瓜); -- ❌ 被阻塞因为 (5,10) 被间隙锁锁住了 INSERT INTO product VALUES (12, 梨); -- ❌ 被阻塞因为 (10,15) 被间隙锁锁住了 INSERT INTO product VALUES (20, 草莓); -- ✅ 成功15 之后没有间隙锁这就是间隙锁的实际效果——不存在的记录也插不进去因为间隙被你锁住了。五、间隙锁的两个重要脾气脾气一兼容性非常佛系多个事务可以在同一个间隙上同时加间隙锁它们之间不会冲突。间隙锁的敌人是插入操作不是另一个间隙锁。-- 事务 A给 (5,10) 加间隙锁 -- 事务 B也给 (5,10) 加间隙锁 ← 没问题不冲突 -- 事务 A 或 B想插入 id7 ← 等吧你脾气二只在可重复读RR级别下生效间隙锁是 MySQL InnoDB 在REPEATABLE READ隔离级别下的产物。如果你把隔离级别降到 READ COMMITTED间隙锁就退场了幻读问题也随之而来。-- 查看当前隔离级别 SELECT transaction_isolation; -- 间隙锁生效的场景 -- 隔离级别 REPEATABLE READInnoDB 默认 -- 使用了 FOR UPDATE / LOCK IN SHARE MODE 等锁定读这就是为什么 InnoDB 在 RR 级别下能拍着胸脯说我能解决幻读——间隙锁就是它的底气。六、间隙锁的坑——别被它咬了一口间隙锁虽好但用不好也容易踩坑。面试官可能会顺着追问坑1大范围更新会锁住巨大间隙-- 假设表里有 id1 和 id1000000 两条记录 DELETE FROM product WHERE id 0; -- 间隙锁会锁住整个 (1, ∞) 范围其他插入全部堵死坑2间隙锁 插入意向锁可能造成死锁-- 事务 A锁定间隙 (5,10)想插入 id6 -- 事务 B锁定间隙 (5,10)想插入 id7 -- 两方都持有间隙锁又都在等对方释放间隙锁才能插入 → 死锁MySQL 检测到死锁后会自动回滚其中一个事务但你的业务代码需要处理好这个异常。七、一张全景总结表维度Record LockGap LockNext-Key Lock锁什么一条存在的记录记录之间的间隙记录 它前面的间隙防什么脏读、不可重复读幻读两者都防冲突对象写操作插入操作写操作 插入操作唯一索引等值查询时退化为 Record Lock可能退化为 Record Lock—生效前提锁定读RR 隔离级别 锁定读RR 隔离级别 锁定读八、常见面试追问Q1唯一索引上做等值查询还会有间隙锁吗如果查询的值存在比如WHERE unique_key 10并且 10 存在Next-Key Lock 会退化为 Record Lock只锁这一行不加间隙锁——因为唯一索引保证了不会有第二条 id10 的记录不需要锁间隙。如果查询的值不存在比如WHERE unique_key 7但表里没有 7那会退化为 Gap Lock锁住它应该在的那个区间防止别人插入。Q2间隙锁锁住的范围怎么确定用索引做范围查询时锁定的是扫描到的索引记录及它们之间的空隙。比如WHERE id BETWEEN 5 AND 10会锁住 (1,5]、(5,10]、(10,15]——即找到的第一条记录的前一个间隙到找到的最后一条记录的后一个间隙。Q3能不能禁用间隙锁能但不推荐随便这么做把隔离级别降到READ COMMITTED——间隙锁就没了但幻读就来了。开启innodb_locks_unsafe_for_binlog参数MySQL 5.7 之前。一般来说如果你用的是 MySQL 主从复制 RR 级别老老实实开着间隙锁是最好的选择。九、一句话总结记录锁告诉别人有主的别动间隙锁告诉别人没主的也别动。记住这句话再配合上面那张锁家族对比图面试官就知道你不仅背了八股文而且是真理解了。