服务覆盖:昆明·曲靖·玉溪·保山·昭通·丽江·普洱·临沧·楚雄·红河·文山·西双版纳·大理·德宏·怒江·迪庆

my.cnf配置

eycit 2026-04-20 0 次阅读 操作指南
---

theme: default themeName: "默认主题" title: "MySQL死锁把应用卡死?一张图教你读懂死锁日志,根因定位只需5分钟"


前言

生产环境最怕两种数据库问题:一是慢查询拖垮性能,二是死锁导致业务直接报错。尤其死锁,它不像慢查询那样给你缓冲时间——一旦触发,事务直接回滚,业务接口瞬间返给你一个"Deadlock found"错误。多数运维看到死锁日志就头皮发麻,其实只要掌握正确的分析方法,定位根因也就是一泡尿的功夫。今天就把我的排查套路完整抖出来,看完你也能独立分析死锁。

死锁是怎么形成的

先铺垫几个基本概念,防止后面看日志时懵圈。

事务:一组原子性SQL操作,要么全部成功,要么全部回滚。MySQL默认autocommit=1,每条SQL自动提交一个事务。 行锁:InnoDB引擎的行级锁,锁定的是表中的具体行数据。锁的是索引记录,不是整行数据。 GAP锁:间隙锁,锁定的是索引记录之间的空隙,防止其他事务插入新记录。 死锁形成条件

1. 互斥:资源只能被一个事务占用 2. 占有并等待:事务已经持有资源,还想申请其他资源 3. 不可抢占:资源不能被强制抢走,只能主动释放 4. 循环等待:形成事务之间的等待环

说人话:事务A锁住了记录1,等着拿记录2;事务B锁住了记录2,等着拿记录1。互相等对方放手,就死锁了。

死锁日志怎么读

MySQL会自动检测死锁并回滚其中一个事务,同时在错误日志中记录详细的死锁信息。来看一个典型的死锁日志:

* (1) TRANSACTION:

TRANSACTION 12345678, ACTIVE 10 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 999, OS thread handle 12345, query id 888 localhost root update INSERT INTO order_detail (order_id, product_id, quantity) VALUES (1001, 2001, 5)

* (2) TRANSACTION:

TRANSACTION 12345679, ACTIVE 8 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 1000, OS thread handle 12346, query id 889 localhost root update INSERT INTO order_detail (order_id, product_id, quantity) VALUES (1002, 2002, 3)

* (2) TRANSACTION 12345679 HOLDS THE LOCK(S) RECORD LOCKS space id 88 page no 3 n bits 72 index PRIMARY of table `shop`.`order_detail` trx id 12345679 lock_mode X locks rec but not gap

Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 00002711; asc '2001';; 1: len 6; hex 0000000002c1; asc '2001';;

* (1) TRANSACTION 12345678 WAITING FOR THIS LOCK TO BE GRANTED

RECORD LOCKS space id 88 page no 3 n bits 72 index PRIMARY of table `shop`.`order_detail` trx id 12345678 lock_mode X locks rec but not gap waiting

这段日志信息量很大,我们来拆解:

TRANSACTION 1 正在执行INSERT,持有锁并等待另一个锁 TRANSACTION 2 正在执行INSERT,持有锁X,阻塞了事务1

看最后的`lock_mode X locks rec but not gap waiting`:X是排他锁,`not gap`说明是记录锁(行锁),`waiting`表示正在等待这个锁。

核心矛盾:两个事务都在插入记录,但插入的记录之间形成了循环等待。

常见死锁模式

模式一:主键索引死锁

两个事务以相反顺序更新主键:

-- 事务1

START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; -- 先锁id=1 UPDATE account SET balance = balance + 100 WHERE id = 2; -- 等锁id=2

-- 事务2(同时执行) START TRANSACTION; UPDATE account SET balance = balance - 200 WHERE id = 2; -- 先锁id=2 UPDATE account SET balance = balance + 200 WHERE id = 1; -- 等锁id=1

解决:统一SQL执行顺序,按主键顺序更新

模式二:非主键索引死锁

-- 事务1

UPDATE user SET status = 1 WHERE phone = '13800138000'; -- 锁索引A DELETE FROM user WHERE id = 10; -- 锁主键

-- 事务2 DELETE FROM user WHERE id = 10; -- 锁主键 UPDATE user SET status = 1 WHERE phone = '13800138000'; -- 锁索引A

两个事务操作的是不同索引,但最终都要访问对方锁住的主键,形成循环等待。

模式三:GAP锁导致的死锁

-- 事务1

INSERT INTO order_detail (order_id, product_id) VALUES (100, 200);

-- 事务2(同时插入相同记录) INSERT INTO order_detail (order_id, product_id) VALUES (100, 200);

两个事务同时插入相同唯一键值,后执行的事务会等待前一个事务释放GAP锁,如果前事务回滚,后事务获取锁继续插入成功——这本身不是死锁,但如果配合其他操作就可能形成死锁。

如何主动排查死锁

1. 开启死锁详细信息日志

# my.cnf配置

innodb_print_all_deadlocks = 1 innodb_status_output = ON innodb_status_output_locks = ON

2. 实时查看当前锁状态

-- 查看当前正在等待的锁

SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看当前持有的锁 SELECT * FROM information_schema.INNODB_LOCKS;

-- 查看事务详情 SELECT * FROM information_schema.INNODB_TRX;

3. 开启慢查询日志捕获问题SQL

# my.cnf

slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1

死锁往往发生在高并发+复杂SQL场景,把慢SQL优化了,死锁概率自然下降。

一个真实案例:订单并发扣款死锁

某电商平台做促销活动时,大量用户反馈支付失败,查看错误日志全是"Deadlock found"。

问题分析

  • 业务逻辑:用户下单 → 扣减库存 → 创建订单 → 支付
  • 并发场景:同一商品多人同时购买
  • 死锁位置:库存表`product_stock`的UPDATE语句

根因:两个订单同时扣减同一商品库存,执行顺序相反导致死锁。
-- 订单A:先扣product_id=1,再扣product_id=2

UPDATE product_stock SET stock = stock - 1 WHERE product_id = 1; UPDATE product_stock SET stock = stock - 1 WHERE product_id = 2;

-- 订单B:先扣product_id=2,再扣product_id=1 UPDATE product_stock SET stock = stock - 1 WHERE product_id = 2; UPDATE product_stock SET stock = stock - 1 WHERE product_id = 1;

解决方案

1. 应用层统一扣减顺序:按product_id排序后再执行UPDATE 2. 或者用SELECT FOR UPDATE显式加锁,强制按顺序获取锁

预防死锁的黄金法则

1. 统一操作顺序:所有事务按相同顺序访问资源 2. 缩小锁范围:只锁必要的行,别用SELECT *全表扫描触发间隙锁 3. 降低事务复杂度:事务内SQL越少越好,批量操作考虑拆分 4. 使用低隔离级别:READ COMMITTED比REPEATABLE READ少用GAP锁 5. 加超时机制:业务代码设置锁等待超时,别无限等下去

# Python示例:设置事务超时

cursor.execute("SET innodb_lock_wait_timeout = 10")

结语

死锁不可怕,可怕的是不会看日志。MySQL的死锁日志已经把所有信息都暴露给你了——哪个事务在等哪把锁、锁的是哪个索引、具体是哪条SQL。顺着日志往下挖,80%的死锁都是操作顺序问题,改改SQL写法就能解决。下次遇到Deadlock报错,直接把日志贴过来按着我教的步骤分析,五分钟定位不是梦。


看完还有什么疑问吗?

如果文章没有覆盖到你的情况,欢迎联系我们咨询——免费解答,说清楚再决定要不要服务。

📞 服务热线:13708730161 💬 微信:eyc1689 📧 邮箱:service@eycit.com 🌐 https://www.eycit.com

易云城IT服务,您身边的IT专家。

上一篇
云南用户版:2026年网络安全威胁:最新勒索病毒和攻击手...
下一篇
小白入门:2026年云南IT服务市场趋势分析:市场规模突...