MySQL死锁详解(5大死锁解决方案)

在高并发数据库应用中,死锁是一个非常常见且令人头疼的问题,下面我详解死锁以及解决方案@mikechen

MySQL死锁

MySQL死锁(DeadLock)是指:

两个或多个事务互相持有对方需要的锁,并且都在等待对方释放锁,从而形成无限等待。

MySQL死锁详解(5大死锁解决方案)-mikechen

此时,数据库无法继续执行事务。

以数据库为例,事务 A 持有记录 1 的锁并等待记录 2 的锁。

而事务 B 持有记录 2 的锁并等待记录 1 的锁,此时两个事务便形成了循环等待,最终导致死锁。

 

MySQL死锁解决方案

1. 统一加锁顺序
这是预防死锁最有效的方法之一。

多个事务如果都按照相同的顺序访问和更新资源,就能显著降低循环等待的概率。

MySQL死锁详解(5大死锁解决方案)-mikechen

例如,统一先更新 id 小的记录,再更新 id 大的记录。

-- 事务A 和 事务B 都必须按此顺序编写
START TRANSACTION;
SELECT * FROM users WHERE id=1 FOR UPDATE;
SELECT * FROM accounts WHERE user_id=1 FOR UPDATE;
-- 业务逻辑...
COMMIT;

 

2. 缩短事务
把事务改短,避免在事务里做耗时的 RPC、复杂计算、批量处理或用户交互逻辑。

MySQL死锁详解(5大死锁解决方案)-mikechen

事务越短,锁持有时间越少,死锁窗口也越小。

比如:

  • 调用外部接口;
  • 大量业务计算;
  • 用户交互等待;

事务应尽早开始、尽快提交,以减少锁持有时间。

-- 1. 事务外完成耗时操作
SELECT quantity FROM stock WHERE goods_id=1;  -- 不加锁查询
CALL external_notify();

-- 2. 极短事务
START TRANSACTION;
UPDATE stock SET quantity = quantity - 1 
WHERE goods_id=1 AND quantity >= 1;

UPDATE orders SET status='paid' WHERE id=xxx;
COMMIT;

 

3. 优化索引

确保 SQL 能够命中合适索引,避免全表扫描和过大的锁范围。

-- 建立联合索引
ALTER TABLE orders ADD INDEX idx_user_ct (user_id, create_time);

-- 优化后的SQL(范围大大缩小)
UPDATE orders SET status='paid' 
WHERE user_id = 100 
  AND create_time = '2025-06-09' 
LIMIT 10;

对于高频更新、删除的语句,更要检查执行计划,避免因索引缺失导致锁竞争加剧。

4. 避免大范围 SQL
尽量把“大 SQL”拆成小批次,减少一次事务里锁住的记录数量。

对高并发系统来说,小批量、可重试通常比单次大事务更稳定。

5. 应用层重试
死锁不是“永远失败”,而是可恢复异常。

对于幂等事务,最实用的方式是捕获死锁错误后随机退避并重试。

评论交流
    说说你的看法