行锁、间隙锁、临键锁分别是什么?死锁怎么发生、怎么处理?
结论先行: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)
处理与规避
- 让所有事务按固定顺序访问资源(例如统一按 id 升序加锁);
- 缩短事务、减少锁持有时间,尽量走索引以缩小锁定的行数;
- 报表/统计类长查询改到读已提交或快照读,避开间隙锁;
- 死锁发生后,根据 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 下没命中任何记录也会加锁吗?答:会,为避免并发插入产生幻读,空档区间同样需要间隙锁覆盖。
- 记忆点:记录锁锁“一行”,间隙锁锁“一段空”,临键锁是“记录 + 间隙”复合体;破死锁四板斧——定序、缩短、走索引、可重试。