MySQL:死锁问题分析与解决方案

1、简述

死锁(Deadlock)是指两个或多个事务在执行过程中,因争夺锁资源而造成的一种相互等待的现象,若无外力干预,这些事务将永远无法继续执行。

在 MySQL(主要是 InnoDB 存储引擎)中,死锁是正常现象 ,并非 Bug。InnoDB 具备死锁检测机制 ,当检测到死锁时,会主动回滚代价较小的事务,并返回错误:

text 复制代码
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

2、MySQL 中的锁基础(死锁的前提)

常见锁类型:

  • 行锁(Record Lock)
  • 间隙锁(Gap Lock)
  • 临键锁(Next-Key Lock = 行锁 + 间隙锁)
  • 意向锁(Intention Lock)

⚠️ 死锁通常发生在 行级锁 + 并发事务 场景下。


3、死锁产生的典型场景

场景 1:不同事务,反序加锁(最经典)

示例表
sql 复制代码
CREATE TABLE account (
  id INT PRIMARY KEY,
  balance INT
) ENGINE=InnoDB;

INSERT INTO account VALUES (1, 100), (2, 100);
事务 A
sql 复制代码
BEGIN;
UPDATE account SET balance = balance - 10 WHERE id = 1;
-- 等待
UPDATE account SET balance = balance + 10 WHERE id = 2;
事务 B
sql 复制代码
BEGIN;
UPDATE account SET balance = balance - 10 WHERE id = 2;
-- 等待
UPDATE account SET balance = balance + 10 WHERE id = 1;
死锁分析
事务 已持有锁 正在等待
A id=1 id=2
B id=2 id=1

👉 形成环形等待 → 死锁

场景 2:范围查询 + 更新(间隙锁)

sql 复制代码
BEGIN;
SELECT * FROM orders WHERE user_id = 100 FOR UPDATE;
sql 复制代码
BEGIN;
INSERT INTO orders(user_id, amount) VALUES (100, 99);
  • 第一个事务加了 Next-Key Lock
  • 第二个事务插入被间隙锁阻塞
  • 若双方后续又请求对方持有的锁 → 死锁

场景 3:唯一索引冲突 + 并发插入

sql 复制代码
CREATE TABLE user (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(50) UNIQUE
) ENGINE=InnoDB;
sql 复制代码
INSERT INTO user(email) VALUES ('a@test.com');

多个事务同时插入相同唯一键,可能引发死锁(而不是简单的唯一键冲突)。


4、如何定位 MySQL 死锁?

1️⃣ 查看最近一次死锁信息(最重要)

sql 复制代码
SHOW ENGINE INNODB STATUS;

关注内容:

  • LATEST DETECTED DEADLOCK
  • 哪些 SQL
  • 哪些索引
  • 哪些事务被回滚
text 复制代码
*** (1) TRANSACTION:
UPDATE account SET balance = balance - 10 WHERE id = 1

*** (2) TRANSACTION:
UPDATE account SET balance = balance - 10 WHERE id = 2

2️⃣ 开启死锁日志(线上推荐)

sql 复制代码
SET GLOBAL innodb_print_all_deadlocks = ON;

死锁信息会输出到 MySQL error log,便于长期分析。

3️⃣ 监控指标

  • Innodb_deadlocks
  • Innodb_row_lock_waits
  • Innodb_row_lock_time

5、MySQL 死锁解决方案

✅ 方案 1:统一加锁顺序(最有效)

原则:多个事务访问多行数据时,保证顺序一致

❌ 错误示例:

text 复制代码
事务 A:锁 1 → 锁 2
事务 B:锁 2 → 锁 1

✅ 正确示例:

text 复制代码
事务 A:锁 1 → 锁 2
事务 B:锁 1 → 锁 2

✅ 方案 2:缩小事务范围,减少锁持有时间

sql 复制代码
BEGIN;
SELECT ...;
-- 业务逻辑(耗时)
UPDATE ...
COMMIT;

sql 复制代码
-- 业务逻辑
BEGIN;
UPDATE ...
COMMIT;

✅ 方案 3:使用合适索引,避免全表/范围锁

❌ 无索引导致:

sql 复制代码
UPDATE orders SET status = 1 WHERE user_id = 100;

👉 可能锁大量记录甚至产生间隙锁

✅ 建立索引:

sql 复制代码
CREATE INDEX idx_user_id ON orders(user_id);

✅ 方案 4:降低隔离级别(谨慎)

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • 减少 Gap Lock
  • 适用于 读多写少 场景
  • ⚠️ 注意是否能接受不可重复读

✅ 方案 5:应用层重试(必须)

InnoDB 会自动回滚一个事务,业务应当:

  • 捕获 Deadlock found 异常
  • 进行 有限次数重试
java 复制代码
for (int i = 0; i < 3; i++) {
    try {
        doTransaction();
        break;
    } catch (DeadlockException e) {
        if (i == 2) throw e;
    }
}

6、实践样例

sql 复制代码
BEGIN;

-- 按 id 顺序加锁,避免死锁
SELECT balance FROM account WHERE id IN (1, 2) ORDER BY id FOR UPDATE;

UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;

COMMIT;

关键点:

  • FOR UPDATE
  • ORDER BY
  • 保证锁顺序一致

7、总结

死锁的本质: 并发事务 + 锁 + 不一致的资源获取顺序

  • ✅ 统一加锁顺序
  • ✅ 缩短事务
  • ✅ 合理索引
  • ✅ 控制隔离级别
  • ✅ 应用层重试
  • ✅ 监控 & 日志分析
相关推荐
玩转数据库管理工具FOR DBLENS2 小时前
关系型数据库与非关系型数据库:差异、介绍与市场格局
数据库·oracle·nosql
我科绝伦(Huanhuan Zhou)3 小时前
影响SQL Server性能的关键因素深度解析
数据库·sql server
Lion Long3 小时前
大数据时代的“时间”难题:时序数据库(TSDB)选型避坑指南
大数据·数据库·时序数据库·数据库架构·iotdb·tsdb
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue医院挂号管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
云老大TG:@yunlaoda3603 小时前
华为云国际站代理商NAT的高可用与弹性具体是如何实现的?
服务器·数据库·华为云·负载均衡
虹科网络安全3 小时前
艾体宝产品 | 隆重推出 Haink:Redis 的应用型 AI 智能体
数据库·人工智能·redis
祁思妙想4 小时前
Python中ORM(对象关系映射)的概念与实操---连接数据库
数据库·oracle
高斯的手稿08014 小时前
Django里面,多个APP的url设置,每个APP单独对应HTML设置
数据库·django·html
镜花水月linyi4 小时前
MySQL与Redis缓存一致性方案
redis·后端·mysql