手把手教你分析解决MySQL死锁问题


手把手教你分析解决MySQL死锁问题

文章插图
 
在生产环境中如果出现MySQL死锁问题该如何排查和解决呢,本文将模拟真实死锁场景进行排查,最后总结下实际开发中如何尽量避免死锁发生 。
一、准备好相关数据和环境当前自己的数据版本是8.0.22
mysql> select @@version;+-----------+| @@version |+-----------+| 8.0.22|+-----------+1 row in set (0.00 sec)数据库隔离级别(默认隔离级别)
mysql> select @@transaction_isolation;+-------------------------+| @@transaction_isolation |+-------------------------+| REPEATABLE-READ|+-------------------------+1 row in set (0.00 sec)自动提交关闭
mysql> select @@autocommit;+--------------+| @@autocommit |+--------------+|1 |+--------------+1 row in set (0.00 sec)mysql> set autocommit=0;Query OK, 0 rows affected (0.00 sec)mysql> select @@autocommit;+--------------+| @@autocommit |+--------------+|0 |+--------------+1 row in set (0.00 sec)表结构
这个age为 非唯一的索引,这点对下面整个案例非常重要 。
-- id是自增主键,age是非唯一索引,name普通字段CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`age` int DEFAULT NULL COMMENT '年龄',`name` varchar(255)DEFAULT NULL COMMENT '姓名',PRIMARY KEY (`id`),KEY `idx_age` (`age`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';表中暂时先插入两条数据
手把手教你分析解决MySQL死锁问题

文章插图
 
二、模拟出真实死锁案例开启两个终端模拟事务并发情况,执行顺序以及实验现象如下:
1)事务A执行更新操作,更新成功
mysql> updateuserset name = 'wangwu' where age= 20;Query OK, 1 row affected (0.00 sec)
  1. 事务B执行更新操作,更新成功
mysql> updateuserset name = 'zhaoliu' where age= 10;Query OK, 1 row affected (0.00 sec)3)事务A执行插入操作,陷入阻塞~
mysql> insert into user values (null, 15, "tianqi");
手把手教你分析解决MySQL死锁问题

文章插图
 
4)事务B执行插入操作,插入成功,同时事务A的插入由阻塞变为死锁error 。
insert into user values (null, 30, "wangba");Query OK, 1 row affected (0.00 sec)事务A的插入操作变成报错 。
手把手教你分析解决MySQL死锁问题

文章插图
 
上面四步操作后,我们分别对事务A和事务B进行commit操作 。
mysql> commit;Query OK, 0 rows affected (0.00 sec)我们再来看数据库中表的数据 。
手把手教你分析解决MySQL死锁问题

文章插图
 
我们发现,事务B的所有操作最终都成功了,而事务A的操作因为报错都回滚了 。所以事务A的操作都失败 。
那既然是死锁,为什么回滚事务A,而不是事务B,是随机的还是有机制在里面?
我们可以理解死锁是数据库对事务的保护机制,一旦发生死锁,MySQL会选择相对小的事务(undo较少的)进行回滚 。
三、查看分析死锁日志可以用 show engine innodb status,查看最近一次死锁日志哈,执行后,死锁日志如下(只展示部分日志):
LATEST DETECTED DEADLOCK------------------------2021-12-24 06:02:52 0x7ff7074f8700*** (1) TRANSACTION:TRANSACTION 2554368, ACTIVE 22 sec insertingmysql tables in use 1, locked 1LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2INSERT INTO user VALUES (NULL, 15, "tianqi")*** (1) HOLDS THE LOCK(S):RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554368 lock_mode XRecord lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;;*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554368 lock_mode X locks gap before rec insert intention waitingRecord lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000014; asc;; 1: len 4; hex 80000002; asc;;*** (2) TRANSACTION:TRANSACTION 2554369, ACTIVE 14 sec insertingmysql tables in use 1, locked 1LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2INSERT INTO user VALUES (NULL, 30, "wangba")*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554369 lock_mode X locks gap before recRecord lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000014; asc;; 1: len 4; hex 80000002; asc;;*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554369 lock_mode X insert intention waitingRecord lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;;*** WE ROLL BACK TRANSACTION (1)


推荐阅读