在高并发数据库应用中,死锁是一个非常常见且令人头疼的问题,下面我详解死锁以及解决方案@mikechen
MySQL死锁
MySQL死锁(DeadLock)是指:
两个或多个事务互相持有对方需要的锁,并且都在等待对方释放锁,从而形成无限等待。

此时,数据库无法继续执行事务。
以数据库为例,事务 A 持有记录 1 的锁并等待记录 2 的锁。
而事务 B 持有记录 2 的锁并等待记录 1 的锁,此时两个事务便形成了循环等待,最终导致死锁。
MySQL死锁解决方案
1. 统一加锁顺序
这是预防死锁最有效的方法之一。
多个事务如果都按照相同的顺序访问和更新资源,就能显著降低循环等待的概率。

例如,统一先更新 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、复杂计算、批量处理或用户交互逻辑。

事务越短,锁持有时间越少,死锁窗口也越小。
比如:
- 调用外部接口;
- 大量业务计算;
- 用户交互等待;
事务应尽早开始、尽快提交,以减少锁持有时间。
-- 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. 应用层重试
死锁不是“永远失败”,而是可恢复异常。
对于幂等事务,最实用的方式是捕获死锁错误后随机退避并重试。