行锁、间隙锁、临键锁分别是什么?死锁怎么发生、怎么处理?

结论先行:InnoDB 的锁围绕索引展开:记录锁(Record Lock)锁单条索引记录;间隙锁(Gap Lock)锁两个索引记录之间的区间(含无记录的区间),用于可重复读下防幻读;临键锁(Next-Key Lock)= 记录锁 + 前向间隙锁,是可重复读的默认加锁单位。死锁是两个及以上事务互相持有对方需要的锁且都不释放;MySQL 靠死锁检测回滚代价较小的一方,或用 innodb_lock_wait_timeout 超时兜底。

锁类型对比表

锁定的范围典型场景
记录锁单条索引记录等值命中主键/唯一索引
间隙锁记录之间的区间(含空档)范围条件、RR 下未命中记录的唯一等值
临键锁记录 + 其前间隙RR 默认,范围扫描的加锁单位

死锁示例

-- 事务 A                          事务 B
UPDATE t SET v = 1 WHERE id = 1;   UPDATE t SET v = 2 WHERE id = 2;
UPDATE t SET v = 1 WHERE id = 2;   UPDATE t SET v = 2 WHERE id = 1;
-- A 持有 id=1 等 id=2,B 持有 id=2 等 id=1 → 循环等待 → 死锁(错误码 1213)

处理与规避

  1. 让所有事务按固定顺序访问资源(例如统一按 id 升序加锁);
  2. 缩短事务、减少锁持有时间,尽量走索引以缩小锁定的行数;
  3. 报表/统计类长查询改到读已提交或快照读,避开间隙锁;
  4. 死锁发生后,根据 1213 错误信息里的 LOCK WAIT 链,重试被回滚的事务即可。

有没有索引,锁的范围完全不同

  • 锁加在“索引记录”上:WHERE 条件无索引可用时,RR 下可能退化为锁住大量记录与间隙;
  • 等值命中唯一索引/主键:只加记录锁,不加间隙锁;
  • 等值未命中任何记录(RR 下):加的是间隙锁,防止并发插入填补该空档;
  • 范围查询:对扫描区间逐段加临键锁,直到条件不再满足为止。

加锁顺序不一致是死锁主因

-- 表 t(id 主键, k 唯一键)
-- 事务 A
UPDATE t SET v=1 WHERE id=3;        -- 先锁 id=3
UPDATE t SET v=1 WHERE k='x';       -- 再等 B 持有的 k='x' 锁

-- 事务 B
UPDATE t SET v=2 WHERE k='x';       -- 先锁 k='x'
UPDATE t SET v=2 WHERE id=3;        -- 再等 A 持有的 id=3 锁 → 死锁

常见追问 / 记忆点

  • 追问:锁等待与死锁怎么区分?答:锁等待是排队,超过 innodb_lock_wait_timeout 报 1205;死锁是循环等待,检测到立即回滚并报 1213。
  • 追问:RR 下没命中任何记录也会加锁吗?答:会,为避免并发插入产生幻读,空档区间同样需要间隙锁覆盖。
  • 记忆点:记录锁锁“一行”,间隙锁锁“一段空”,临键锁是“记录 + 间隙”复合体;破死锁四板斧——定序、缩短、走索引、可重试。
笔记加载中…