生成环境 MySQL 死锁分析
背景
生产环境突然出现用户登录不了的问题,通过排除 mysql 数据库出现死锁,通过查询日志
查看 MySQL InnoDB 引擎内部运行状态的诊断报告
show engine INNODB STATUS;
将日志发送给 chatGPT 分析死锁问题。
在高并发系统中,MySQL 死锁是非常常见的问题。本文结合真实 InnoDB 状态日志,从死锁现象、产生原因到解决方案进行系统性分析。
一、什么是死锁?
死锁(Deadlock)指多个事务互相等待对方释放资源,形成循环等待,最终导致事务无法继续执行。
典型模型:
事务A 等待 事务B
事务B 等待 事务A
MySQL InnoDB 检测到死锁后,会自动回滚其中一个事务来解除死锁。
二、案例背景
本次死锁出现在:
collaboration 表
核心 SQL:
INSERT INTO collaboration (...)
VALUES (...)
主键格式类似:
HZ202603060027
HZ202603060029
属于:
✅ 业务递增字符串主键
❌ 非数值自增主键
三、死锁日志核心信息
从 InnoDB 状态可以看到:
① 两个事务同时执行插入
事务1:
TRANSACTION 768989781
INSERT INTO collaboration
事务2:
TRANSACTION 768990060
INSERT INTO collaboration
② 两个事务都持有 S 锁
日志中出现:
lock mode S
说明:
-
S锁是共享锁
-
多个事务可以同时持有
但问题是:
👉 两个事务都在等待升级为 X 锁。
四、死锁形成的核心原因
⭐ 根本原因:热点页竞争
本案例中:
所有 INSERT 都发生在 B+Tree 最右侧页
日志中出现:
supremum
supremum 是 InnoDB 索引页的最大边界虚拟记录。
B+Tree 结构示意
[记录1]
[记录2]
[记录3]
[SUPREMUM]
新数据会不断插入:
SUPREMUM 左侧
如果高并发写入:
多个事务会同时:
-
定位插入位置
-
申请锁
-
尝试升级为写锁
从而形成锁竞争。
五、为什么 INSERT 也会产生死锁?
很多人误以为:
INSERT 不会死锁
这是错误的。
InnoDB 插入时通常涉及:
Record Lock
Gap Lock
Next-Key Lock
Insert Intention Lock
在高并发场景下:
多个事务可能:
同时持有 S 锁
同时尝试升级 X 锁
形成循环等待。
六、为什么自增 ID 可以降低死锁概率?
自增 ID 能有效减少死锁,核心原因不是“也往最后插入”,而是:
⭐ 数据库统一分配 ID
MySQL 通过自增锁机制:
先发号
再写入
避免多个事务:
同时竞争同一个插入位置
⭐ 更重要的是:分散索引页写入
自增主键会:
-
减少热点页竞争
-
降低 gap lock 争抢
-
提高并发写入性能
七、为什么业务递增字符串主键更容易死锁?
例如:
HZ + 日期 + 序列号
存在几个问题:
① 业务系统自己生成 ID
多个应用实例可能:
同时生成相近 ID
然后一起写数据库。
② 字符串排序按字典序
MySQL 索引排序:
按字符比较
而不是时间逻辑
导致:
所有数据集中在索引树右侧
形成热点页。
八、解决死锁的最佳实践(重要 ⭐⭐⭐⭐⭐)
✅ 方案一(最推荐)
改用:
BIGINT AUTO_INCREMENT
同时:
业务ID单独存储
这是大多数互联网系统标准方案。
✅ 方案二:分布式ID
使用:
-
Snowflake ID
-
类雪花算法
特点:
趋势递增
但不完全连续
能减少锁冲突。
✅ 方案三:短事务原则
避免:
一个事务中包含:
查询 + 业务逻辑 + 写入
应该拆成:
查询
业务计算
写入(短事务)
九、总结(最重要)
本案例死锁的本质是:
高并发 INSERT
+
递增字符串主键
+
B+Tree 索引热点页竞争
最终导致:
事务间循环等待
最有效的解决方案是:
改用 BIGINT 自增主键
或 Snowflake 分布式 ID
十、一句话记住死锁本质
MySQL 死锁不是因为锁太多,而是因为多个事务同时争抢同一个数据结构热点位置。
更多推荐




所有评论(0)