连载中 9/22

锁:行锁、间隙锁与一场死锁事故复盘

2026-05-06 · 3225 阅读 · 0 评论 · 0 赞

一场深夜死锁

凌晨两点,老王的库存扣减服务报出 Deadlock found when trying to get lock,QPS 一掉,订单堆积。查下来两个事务的写法都很「无害」,却互相掐死了。这篇先把 MySQL 的锁体系铺开,再复盘这场事故——死锁不可怕,可怕的是看不懂案发现场

三层锁:全局、表、行

命令 / 场景备注
全局锁FLUSH TABLES WITH READ LOCK,全库只读逻辑备份用;InnoDB 场景建议改用 mysqldump --single-transaction(第 21 篇)
表级锁显式表锁、MDL 元数据锁、意向锁、AUTO-INC 锁MDL 最常坑人:长事务 + DDL 互堵(第 19 篇展开)
行级锁记录锁、间隙锁、next-key lockInnoDB 实现,本文主角

行锁的三件套

  • 记录锁(Record Lock):锁住索引上的一条记录。
  • 间隙锁(Gap Lock):锁住两条记录之间的开区间,RR 级别防幻读的武器——别人往这个区间插不进数据。间隙锁之间不冲突(都挡插入,不挡读)。
  • next-key lock:记录锁 + 前面的间隙(前开后闭区间),RR 下当前读的默认加锁单位。等值查询命中唯一索引时退化成纯记录锁。

先立一个最容易翻车的认知:行锁是加在索引上的。WHERE 条件没走索引,InnoDB 没法精确定位,只能对扫过的所有记录加锁(近似锁全表)——所以「更新条件必须走索引」不只是性能问题,是并发安全问题。

快照读与当前读

MVCC 管的是快照读(普通 SELECT,不加锁,读历史版本)。但 SELECT ... FOR UPDATEFOR SHARE、以及所有 update / delete 是当前读:必须读最新版本并加锁。RR 防幻读的完整答案是:快照读靠 MVCC,当前读靠 next-key lock

事故复盘:库存扣减死锁

简化后的案发代码,事务 A 和事务 B 几乎同时执行( goods_id 均不在索引上):

-- 事务 A                      -- 事务 B
BEGIN;                          BEGIN;
UPDATE stock SET num = num - 1
WHERE goods_id = 1001;
                                UPDATE stock SET num = num - 1
                                WHERE goods_id = 1002;
INSERT INTO stock_log (goods_id, num)
VALUES (1001, -1);
                                INSERT INTO stock_log (goods_id, num)
                                VALUES (1002, -1);
-- A 的 insert 等待 B 持有的主键间隙
-- B 的 insert 等待 A 持有的主键间隙 → 死锁

两条 UPDATE 因为没走索引,各扫全表并锁住途中所有 next-key 区间,随后两个 INSERT 都撞进对方持有的间隙,形成环路。MySQL 的死锁检测(innodb_deadlock_detect 默认开)会选代价小的事务回滚,报 Deadlock。案发现场用这条命令回看:

SHOW ENGINE INNODB STATUS;   -- 看 LATEST DETECTED DEADLOCK 段
-- 两个事务分别持有(HOLDS THE LOCK)与等待(WAITING FOR)哪把锁,一目了然
SET GLOBAL innodb_print_all_deadlocks = ON;   -- 把所有死锁记进错误日志

死锁治理四板斧

  • 让更新走索引:锁范围从「扫过的一切」缩到目标行,本案的第一修复。
  • 固定加锁顺序:所有事务按同一顺序访问资源(比如按 goods_id 升序),环路就不成立。
  • 事务要小:锁持有时间短,冲突窗口小;别在事务里做 RPC、慢查询。
  • 兜底重试:捕获死锁异常(错误码 1213)自动重试一次——死锁被回滚的一方没有污染,重试是安全的。

改完索引 + 排序加锁后,老王的死锁率归零。其实 Redis 系列第 8 篇的分布式锁之争里那句「单点不能拍板」,在 MySQL 锁这里有了新注脚:数据库的锁单机自洽,一旦上了主从,另一本账(binlog)的顺序就变得致命——下一篇讲 binlog 与两阶段提交。

咖啡凉了,记得趁热喝。

503

10 年全栈工程师 · 503咖啡馆主理人

#MySQL#锁#间隙锁#next-key lock#死锁

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞