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、总结

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

  • ✅ 统一加锁顺序
  • ✅ 缩短事务
  • ✅ 合理索引
  • ✅ 控制隔离级别
  • ✅ 应用层重试
  • ✅ 监控 & 日志分析
相关推荐
知识即是力量ol1 天前
基于 Redis 实现白名单,黑名单机制详解及应用场景
数据库·redis·缓存
zhihuaba1 天前
使用PyTorch构建你的第一个神经网络
jvm·数据库·python
u0109272711 天前
Python Web爬虫入门:使用Requests和BeautifulSoup
jvm·数据库·python
小光学长1 天前
基于ssm的农业管理系统8y15w544(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库
Mr_Xuhhh1 天前
MySQL表的增删改查(CRUD)操作详解
数据库·windows
定偶1 天前
MySQL安装
数据库·mysql
Zzzzmo_1 天前
【MySQL】数据库约束 及 表的设计
数据库·mysql
码云数智-大飞1 天前
Oracle RAS:AI时代守护企业数据安全的智能盾牌
数据库·人工智能·oracle
bubuly1 天前
软件开发全流程注意事项:从需求到运维的全方位指南
大数据·运维·数据库
我真的是大笨蛋1 天前
Redo Log详解
java·数据库·sql·mysql·性能优化