mysql在rr级别下update锁间隙是为了防止幻读,本质是innodb用间隙锁锁定索引中不存在记录的空白区间,如id=3不存在且邻近值为1和5时锁定(1,5),阻塞该区间内的insert。

UPDATE 为什么在 RR 级别下锁间隙
因为 InnoDB 在 REPEATABLE READ 隔离级别下默认用间隙锁(Gap Lock)防止幻读,而 UPDATE 的加锁逻辑和 SELECT FOR UPDATE 一致:它先定位索引位置,再决定锁什么。只要 WHERE 条件没精确命中「存在且可唯一确定的索引记录」,就会退化为锁间隙。
- 唯一索引等值查询(
=)且记录存在 → 只加记录锁(Record Lock),不锁间隙 - 唯一索引等值查询但记录不存在 → 锁前后两个实际索引值之间的间隙,例如主键有
1和5,执行UPDATE t SET x=1 WHERE id=3会锁(1, 5) - 非唯一索引或范围条件(
>、BETWEEN、LIKE 'abc%')→ 默认加Next-Key Lock(记录锁 + 间隙锁),哪怕只更新一行
哪些 UPDATE 语句最容易意外锁住大范围间隙
不是语句写了“WHERE id = ?”就一定安全——关键看它是否真的走索引、是否能精确定位。常见踩坑点:
-
WHERE user_id = 123 AND status = 'unpaid':如果只有user_id单列索引,status无索引,优化器会回表过滤,此时仍按user_id索引加 Next-Key 锁,可能锁住整个user_id = 123对应的所有索引间隙 -
WHERE create_time > '2024-01-01':时间字段没索引 → 全表扫描 → 主键索引上加全表 Next-Key 锁,等效于锁整张表 -
WHERE name LIKE 'iphone%':普通索引前缀匹配 → 范围扫描 → 锁所有匹配前缀的索引项及其间隙,比如('iphone', 'ipad')、('ipad', 'mac')等多个间隙
组合索引下 UPDATE 的间隙锁范围怎么算
组合索引遵循最左前缀原则,间隙锁范围取决于 WHERE 条件能“用到索引的哪一段”。例如索引是 (category, publish_year, title):
-
WHERE category = '计算机' AND publish_year = 2021 AND title = '数据库原理'→ 精确匹配全部三列 → 只锁该行记录(记录锁),一般不扩散间隙锁 -
WHERE category = '计算机' AND publish_year = 2021→ 匹配到索引前两列,第三列未定 → 锁所有category='计算机' AND publish_year=2021对应的索引范围,包括这些记录之间的间隙(如('计算机',2021,'MySQL') → ('计算机',2021,'Redis')之间的空档) -
WHERE publish_year = 2021→ 未用最左列category→ 索引失效 → 全表扫描 → 锁整张表
如何快速判断一条 UPDATE 是否触发了间隙锁
别猜,直接看执行计划 + 实际行为:
- 执行
EXPLAIN FORMAT=TRADITIONAL your_update_statement,重点看key是否为NULL或预期索引名,rows是否远大于你认为应影响的行数 - 在事务中执行该 UPDATE 后,立刻在另一个会话尝试插入「看起来不冲突」的数据,比如
UPDATE t WHERE id = 3(id=3 不存在),再试INSERT INTO t VALUES (3, ...)—— 若被阻塞,说明已加间隙锁 - 查
information_schema.INNODB_TRX和INNODB_LOCKS(MySQL 5.7+)或performance_schema.data_locks(8.0+),确认锁类型是RECORD还是GAP或NEXT-KEY
间隙锁本身不可禁用,但它的影响可以规避:确保 WHERE 条件走有效索引、避免在高并发写场景用范围条件更新、必要时临时降级隔离级别为 READ COMMITTED(注意 binlog_format 必须为 ROW 才安全)。











