如何解决MySQL死锁(看懂MySQL锁日志)

本文详细描述了MySQL死锁的产生、实操中的死锁示例、如何通过日志定位死锁原因,以及常见的死锁场景,如selectforupdate和批量update引发的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有时候系统在生产运行着,会突然爆出

[40001][1213] Deadlock found when trying to get lock; try restarting transaction

这个时候每个人都会很紧张,因为死锁会影响DB性能,严重时甚至拖垮整个系统。在实际的环境中,很多服务会共用一个数据库,一旦数据库挂了,基本就是P0事故。

那么,死锁发生时,我们如何定位到死锁发生的SQL?

死锁排查

实操前置准备

磨刀不误砍柴功,我们先准备下实验环境。

首先创建一张表:

create table users(
    id int comment "id",
    age int comment '年龄',
    id_no int comment '身份证号',
    UNIQUE KEY `uk_task_obj` (`id_no`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='用户表';

写入数据:

insert into users values (1,18,1);
insert into users values (2,18,2);
insert into users values (3,18,3);

接着,我们开两个事务:

事务一:

begin;
select * from users where id_no=1 for update;
select * from users where id_no=3 for update ;
commit;

事务二:

begin;
select * from users where id_no=3 for update ;
select * from users where id_no=1 for update ;
commit ;

可以看到,两个事务执行的语句都一样,只不过顺序不一样,我们按照以下时序去执行时,终端会提示Deadlock found when trying to get lock; try restarting transaction

事务一事务二

beigin;

select * from users where id_no=1 for update;

begin;

select * from users where id_no=3 for update ;
select * from users where id_no=3 for update ;
select * from users where id_no=1 for update ;(此时死锁发生)

查看日志

要定位死锁发生的原因,我们需要知道,是哪些事务持有了哪些锁,哪些事务又互相阻塞。

我们可以通过

SHOW ENGINE INNODB STATUS;

来查看死锁发生时的日志

日志分析

当执行SHOW ENGINE INNODB STATUS时,MySQL返回如下日志:

日志内容很多,我们主要关注:LATEST DETECTED DEADLOCK 这一部分。我们逐步讲解下这一部分的日志。

日志解释

LATEST DETECTED DEADLOCK

------------------------

2024-03-18 21:07:00 0x16be8b000

*** (1) TRANSACTION:

TRANSACTION 1836, ACTIVE 10 sec starting index read

TRANSACTION 1836:1836代表事务id;

active 10 sec:表示活跃时间

LOCK WAIT 4 lock struct(s), heap size 1128, 3 row lock(s)

MySQL thread id 17, OS thread handle 6121680896, query id 2044 localhost 127.0.0.1 root statistics

不重要,忽略

/* ApplicationName=GoLand 2023.2.2 */ select * from users where id_no=3 for update

这个事务执行的sql语句

*** (1) HOLDS THE LOCK(S):

RECORD LOCKS space id 2 page no 5 n bits 72 index uk_task_obj of table `test`.`users` trx id 1836 lock_mode X locks rec but not gap

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 80000001; asc     ;;

 1: len 6; hex 000000000200; asc       ;;

HOLDS THE LOCK:表示当前事务持有的锁。

RECORD LOCKS:表明是记录锁

space id 2 page no 5 n bits 72 :这是mysql底层存储的位置,我们可以不理解。

index uk_task_obj of table `test`.`users` trx id 1836 lock_mode X locks rec but not gap:表明是索引uk_task_obj上的锁。X锁代表互斥锁,rec but not gap表示是记录锁不是间隙锁。

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 2 page no 5 n bits 72 index uk_task_obj of table `test`.`users` trx id 1836 lock_mode X locks rec but not gap waiting

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 80000003; asc     ;;

 1: len 6; hex 000000000202; asc       ;;

WAITING FOR THIS LOCK TO BE GRANTED:这句话就说了,这个事务在等待索引uk_task_obj上的一个记录锁,下面是等待的锁信息。

*** (2) TRANSACTION:

TRANSACTION 1837, ACTIVE 7 sec starting index read

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1128, 3 row lock(s)

MySQL thread id 18, OS thread handle 6120566784, query id 2058 localhost 127.0.0.1 root statistics

/* ApplicationName=GoLand 2023.2.2 */ select * from users where id_no=1 for update

第一个事务在等待锁,这个时候提到第二个事务了。事务id是1837,执行的语句是select * from users where id_no=1 for update

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 2 page no 5 n bits 72 index uk_task_obj of table `test`.`users` trx id 1837 lock_mode X locks rec but not gap

Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 80000003; asc     ;;

 1: len 6; hex 000000000202; asc       ;;

1837事务持有的锁也是记录锁,也是在唯一索引uk_task_obj上。

锁的信息如下:

0: len 4; hex 80000003; asc     ;;

 1: len 6; hex 000000000202; asc       ;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 2 page no 5 n bits 72 index uk_task_obj of table `test`.`users` trx id 1837 lock_mode X locks rec but not gap waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 80000001; asc     ;;

 1: len 6; hex 000000000200; asc       ;;

WAITING FOR THIS LOCK TO BE GRANTED:等待锁

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 80000001; asc     ;;

 1: len 6; hex 000000000200; asc

等待一个记录锁,可以看到,事务1836和1837持有和等待的锁是相反的,因而发生死锁

*** WE ROLL BACK TRANSACTION (2)

MySQL选择回滚第二个事务

常见的MySQL死锁场景

上面的例子,是最常见的由于select for update产生的死锁问题,以下还有几种发生死锁的场景。

批量update

例如:insert into users values(1,2,3),(2,2,3),(3,3,3)和insert into users values(3,2,3),(2,2,3),(1,3,3)同时执行时,会由于锁的冲突会导致死锁。表面是看insert into users values(1,2,3),(2,2,3),(3,3,3)是一条语句,实际上MySQL并不是一次性加完全部锁,它会按照SQL的书写顺序逐步加锁。解决方法是在批量插入之前,我们按一定规则排序,只要两条sql按相同的顺序加锁便不会有死锁问题

update退化为共享锁

在MySQL中,update语句加的是排它锁,也就是X锁。如果此时另外一个事务正在执行select语句,对同一个目标加了共享锁之后,执行update的事务会由于加X锁失败,转而变为共享锁。

此时会发生如下情况:

事务1执行完第一步之后,已经持有A的共享锁;

事务2执行第二步,由于排它锁加锁失败,转为持有A的共享锁,同时等待事务1释放共享锁;

事务3执行第三步,要将共享锁升级为排它锁,等待事务2释放共享锁。

此时事务1和2发生了循环等待,导致死锁发生。

总结:

  1. 通过SHOW ENGINE INNODB STATUS查看MySQL死锁日志
  2. select for update是最常见的死锁场景
  3. 批量update时注意加锁顺序、小心update的排它锁退化成共享锁导致死锁发生

诚意满满系列每一篇都是精挑细选,从大众知识点到原理再到具体实现,争取把一个知识点从头到尾完整讲下来,足以应付面试与工作。让读者读完之后能够有一种:“这个知识我看这一篇就够了”的感觉是本系列最大愿望。

对于本人而言,在之前的学习中也发现,八股文讲得细致但不系统,而系统的学习往往又宽泛不细致,所以也打算取长补短,互相结合一下,欢迎大家收藏关注,持续更新。

<think>好的,用户现在问的是MySQL死锁记录。之前他们问过Windows的内存和CPU告警记录,现在转向数据库死锁,可能是在系统监控之后遇到了数据库层面的问题,或者是想全面了解不同系统的故障排查方法。 首先,我需要确定用户的需求是什么。他们可能遇到了死锁问题,需要知道如何检测和分析,或者是想提前了解如何监控。用户可能是开发人员、DBA或者运维人员,需要具体的操作步骤和工具。 接下来,我需要回忆MySQL死锁相关的知识点。死锁发生的条件,如何记录,如何查看日志,以及如何分析解决。记得MySQL的InnoDB引擎有死锁检测机制,会回滚其中一个事务,并记录到错误日志。需要确认日志的开启状态和位置。 用户可能需要知道如何启用死锁日志,查看最近的死锁信息,以及分析这些信息的方法。还要考虑不同MySQL版本的差异,比如5.6之后才有innodb_print_all_deadlocks参数,可以记录所有死锁到错误日志,而不仅限于最后一个。 另外,用户可能对如何解读死锁日志中的内容不太清楚,需要解释TRANSACTION、WAITING FOR、HEX信息等。还要提到使用SHOW ENGINE INNODB STATUS的命令来获取最近的死锁信息,但可能只保留最后一个死锁的情况。 还需要考虑如何预防死锁,比如事务设计、索引优化、定顺序等,但用户的问题主要是记录和分析,所以这部分可能需要简要提及,但重点还是在记录和排查步骤。 可能用户在实际操作中遇到了困难,比如日志没有开启,或者看不懂输出的信息,所以需要详细说明如何配置参数,以及如何逐步分析日志中的内容。同时,提醒用户定期监控日志,设置报警,避免死锁影响系统性能。 最后,可能需要推荐一些工具,比如pt-deadlock-logger,或者监控系统集成,帮助用户自动化死锁监控和分析。确保回答结构清晰,步骤明确,让用户能一步步跟着操作。</think>以下是关于 **MySQL 死锁记录** 的详细解析,涵盖监控、日志分析及解决方案: --- ### **一、死锁的基本原理** MySQL 的 InnoDB 存储引擎通过**自动检测机制**处理死锁: - 触发条件:两个或多个事务互相持有对方需要的。 - 处理方式:InnoDB 会强制回滚其中一个事务(通常选择回滚代价更小的事务),并抛出错误 `ERROR 1213 (40001): Deadlock found`。 --- ### **二、死锁记录的存储位置** #### 1. **错误日志(Error Log)** - **默认路径**:`${datadir}/hostname.err`(可通过 `SHOW VARIABLES LIKE 'log_error';` 查询) - **记录内容**: ```log LATEST DETECTED DEADLOCK ------------------------ 2024-01-01 12:00:00 0x7f8e3c123700 *** (1) TRANSACTION: TRANSACTION 12345678, ACTIVE 5 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 100, OS thread handle 123456, query id 987654 localhost root updating UPDATE table1 SET col1 = 1 WHERE id = 10 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 789 page no 3 n bits 72 index PRIMARY of table `test`.`table1` trx id 12345678 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 87654321, ACTIVE 3 sec starting index read mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 101, OS thread handle 654321, query id 987655 localhost root updating UPDATE table2 SET col2 = 2 WHERE id = 20 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 789 page no 4 n bits 72 index PRIMARY of table `test`.`table2` trx id 87654321 lock_mode X locks rec but not gap *** WE ROLL BACK TRANSACTION (2) ``` - **关键字段解释**: - `TRANSACTION`:事务ID及状态。 - `LOCK WAIT`:事务等待的类型(如 `X` 排他、`S` 共享)。 - `RECORD LOCKS`:定的记录位置(通过 `space id` 和 `page no` 定位)。 #### 2. **InnoDB 状态信息** - 通过命令获取实时死锁信息: ```sql SHOW ENGINE INNODB STATUS\G ``` - **注意**:默认仅保留**最后一次死锁记录**。 --- ### **三、配置死锁日志记录** #### 1. **启用完整死锁日志** - 修改配置文件 `my.cnf`: ```ini [mysqld] innodb_print_all_deadlocks = ON # 记录所有死锁到错误日志MySQL 5.6+) log_error_verbosity = 3 # 记录详细错误信息 ``` - 重启 MySQL 生效。 #### 2. **监控工具** - **pt-deadlock-logger**(Percona Toolkit): ```bash pt-deadlock-logger --user=root --password=xxx --socket=/tmp/mysql.sock ``` - **性能模式(Performance Schema)**: ```sql SELECT * FROM performance_schema.data_locks; -- 查看当前状态 SELECT * FROM performance_schema.events_statements_history; -- 关联SQL语句 ``` --- ### **四、死锁分析步骤** #### 1. **定位死锁事务** - 从错误日志中提取死锁信息,重点关注: - **竞争的资源**:定记录的具体位置(`space id` + `page no`)。 - **事务的SQL语句**:引发死锁的 `UPDATE`/`SELECT ... FOR UPDATE` 等操作。 #### 2. **复现与调试** - 使用 `innodb_lock_monitor`(已弃用,推荐通过 Performance Schema 替代): ```sql CREATE TABLE innodb_lock_monitor (id INT) ENGINE=InnoDB; -- 传统方式启用监控 ``` - 通过 `EXPLAIN` 分析SQL执行计划,检查索引使用情况。 #### 3. **代码层优化** - **定顺序**:确保所有事务按相同顺序访问表或行。 - **事务粒度**:减少事务执行时间,避免长事务。 - **索引优化**:添加合适的索引,减少全表扫描导致的冲突。 --- ### **五、典型死锁案例** #### **案例1:交叉更新导致死锁** - **场景**: ```sql -- 事务A UPDATE table1 SET col1 = 1 WHERE id = 10; UPDATE table2 SET col2 = 2 WHERE id = 20; -- 事务B UPDATE table2 SET col2 = 2 WHERE id = 20; UPDATE table1 SET col1 = 1 WHERE id = 10; ``` - **解决**:统一事务中表的操作顺序。 #### **案例2:Gap冲突** - **场景**:范围查询(如 `WHERE id BETWEEN 10 AND 20`)在 `REPEATABLE READ` 隔离级别下产生Gap。 - **解决**:改用 `READ COMMITTED` 隔离级别或优化查询条件。 --- ### **六、高级工具推荐** 1. **Percona Toolkit** - `pt-deadlock-logger`:实时记录死锁事件到数据库表。 2. **MySQL Enterprise Monitor** - 图形化展示死锁频率及影响范围。 3. **Prometheus + Grafana** - 通过 `mysql_exporter` 监控 `innodb_deadlocks` 指标。 --- ### **七、预防策略** 1. **事务设计**: - 使用 `SELECT ... FOR UPDATE NOWAIT`(MySQL 8.0+)避免等待。 - 尽量使用基于主键或唯一索引的条件更新。 2. **监控告警**: - 定期扫描错误日志,设置阈值告警(如每分钟超过3次死锁触发通知)。 3. **压力测试**: - 使用 `sysbench` 或 `jmeter` 模拟高并发场景,提前暴露潜在死锁。 --- 通过上述方法,可有效记录、分析并减少 MySQL 死锁问题。对于高频死锁场景,建议结合**应用层重试机制**(如 exponential backoff)提升系统容错能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值