大数跨境
0
0

MySQL 死锁分析与定位技巧

MySQL 死锁分析与定位技巧 Linux运维技术之路
2025-12-02
4
导读:MySQL 死锁分析与定位技巧在实际业务中,只要涉及 高并发 + 多表更新 + 范围查询 + 索引使用不当

 










 

MySQL 死锁分析与定位技巧

在实际业务中,只要涉及 高并发 + 多表更新 + 范围查询 + 索引使用不当,死锁几乎是必然出现的。

死锁会带来:

  • • 请求卡住、超时
  • • 更新失败
  • • 用户投诉
  • • QPS 突然下降

一、什么是死锁?(面试/讲解必备一句话)

两个或多个事务互相等待对方持有的锁,导致所有事务都无法继续执行,这就是死锁。

简单示例:

  • • 事务 A 锁住 row 1,等待 row 2
  • • 事务 B 锁住 row 2,等待 row 1

双方都不释放 → 永远卡住。

InnoDB 会自动检测死锁,挑一个事务回滚,返回:


   
    
   Deadlock found when trying to get lock

二、为什么会发生死锁?最常见的 6 种场景

  1. 1. 更新顺序不一致
    • • A: 先更新 user,再更新 order
    • • B: 先更新 order,再更新 user
      → 互相等待
  2. 2. 范围查询 + 记录锁 + 间隙锁
    • • 特别是:WHERE age BETWEEN 10 AND 20 FOR UPDATE
  3. 3. 缺少索引导致锁住更多行
    • • 明明是更新 1 行,却锁住成千上万行
  4. 4. 大事务未及时提交
    • • 一个事务持有锁时间长 → 只要另一个事务访问重叠范围就容易死锁
  5. 5. 外键导致隐式锁
    • • 更新父表
    • • 更新子表
      → 额外产生排他锁
  6. 6. 插入同一个 GAP(插入意向锁)
    • • 高频插入容易死锁

你遇到的 90% 死锁都属于以上类型。


三、出现死锁时,第一步要看什么?

MySQL 提供一个神级命令:


   
    
   SHOW ENGINE INNODB STATUS\G;

其中包含:

  • • 死锁发生时间
  • • 参与死锁的事务
  • • 各事务持有的锁
  • • 正在等待的锁
  • • SQL 语句
  • • 锁的类型(Record/GAP/Next-Key)
  • • 索引信息

这一段日志就是排查死锁的核心材料。


四、死锁日志如何看?(最关键)

下面我给你演示一段真实死锁日志,并手把手教你怎么看。


1)锁等待信息


   
    
   *** (1) TRANSACTION:
    WAITING FOR THIS LOCK TO BE GRANTED:
    RECORD LOCKS space id 123 page no 45 n bits 72 index `idx_age` of table `user`
    lock_mode X locks gap

含义:

  • • 事务 1 在等待一个 间隙锁(Gap Lock)
  • • 锁位置:索引 idx_age
  • • 说明:这个 SQL 是范围查询

2)持有锁的信息


   
    
   *** (2) TRANSACTION:
    HOLDS THE LOCK:
    RECORD LOCKS space id 123 page no 45 index `idx_age`
    lock_mode X

事务 2 持有锁。


3)关键 SQL

通常在日志底部:


   
    
   *** (1) WAITING FOR:
UPDATE user SET score = score + 1 WHERE age BETWEEN 10 AND 20;

   
    
   *** (2) HOLDS THE LOCK(S):
INSERT INTO user (age, name) VALUES (15, 'Tom');

这就非常清楚了:

  • • 事务 1:范围更新(Next-Key Lock)
  • • 事务 2:插入(Insert Intention Lock)

引发死锁。


五、通用的死锁定位步骤(你可以直接用)

无论哪个业务,无论哪个库,这 5 步必能定位问题:


① 执行:查看死锁日志


   
    
   SHOW ENGINE INNODB STATUS\G;

记录:发生地点、索引、锁类型


② 复制出参与死锁的 SQL

把日志中的:

  • • “WAITING FOR”
  • • “HOLDS THE LOCKS”

两部分 SQL 全部复制到你的文档里。


③ 根据锁类型判断问题来源

锁类型含义:

锁类型
含义
Record Lock
单行
Gap Lock
空隙(范围锁)
Next-Key Lock
行 + Gap
Insert Intention Lock
插入竞争

④ 检查索引是否正确使用

无索引会导致:

  • • 表扫描
  • • 锁住整个区间
  • • 立刻死锁

如果 SQL 走错索引,基本可以判定这里就是死锁根源。


⑤ 重现死锁,确认优化方案

把业务 SQL 用两个会话复现一遍(加入 SLEEP),100% 可以还原。


六、如何彻底避免死锁?(最重要的部分)


1)固定访问顺序(最有效)

A、B 两个事务更新多表时必须保持顺序一致:

  • • 全按 user → order
  • • 或全按 order → user

避免交叉等待,是最强杀手锏。


2)把大事务拆小,尽快提交

不要:

  • • 在事务内 sleep
  • • 在事务内执行大量逻辑
  • • 在事务里循环上千次操作

锁持有时间越长 → 死锁率越高。


3)明确加锁范围,避免范围查询

尽量使用:


   
    
   WHERE id = 10       -- 精确索引命中,不会触发 gap lock

避免:


   
    
   WHERE id > 10 AND id < 100

4)保持索引设计正确

缺少索引会导致锁住更多行,引发死锁。

重点关注:

  • • WHERE 使用的字段必须有索引
  • • 尽量使用覆盖索引
  • • 避免 MySQL 使用错误的索引(可以加 FORCE INDEX)

5)减少并发写热点

热点行更新导致:

  • • 排他锁冲突
  • • 死锁概率陡增

优化方式:

  • • 拆分热点字段
  • • 把计数操作用 Redis
  • • 使用分布式锁分流写请求

6)关闭不必要的 gap lock(使用 RC 隔离级别)

在 RC 下:

  • • Gap Lock 不会锁范围
  • • 死锁大幅减少

设置方式:


   
    
   SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

适用于:

  • • 电商
  • • 内容类系统
  • • 高并发业务

(但严重依赖金融强一致性的系统不适用)

七、总结

MySQL 死锁的本质是事务互相等待锁。
InnoDB 会自动检测并回滚其中一个事务。
分析死锁时主要看:索引、锁类型、范围锁、SQL 访问顺序。
解决死锁最有效的方法是:统一访问顺序、建立正确索引、减少范围更新、缩小事务规模。

 



 

 


往期回顾

【声明】内容源于网络
0
0
Linux运维技术之路
专注运维架构、高可用、高并发、高性能、大数据、容器化、数据库、python、devops等开源技术和实践的分享。
内容 347
粉丝 0
Linux运维技术之路 专注运维架构、高可用、高并发、高性能、大数据、容器化、数据库、python、devops等开源技术和实践的分享。
总阅读710
粉丝0
内容347