MySQL死锁记录(INSERT INTO ... SELECT.... )

一、前言

在线上发布新功能后,追踪了日志记录,发现有死锁的报错记录。因业务复杂,仅简化问题,先将问题简化复原。特在此记录解决分析过程。

先抛出问题,后附解决方案;

事务A 事务B
1、 insert into user_01 (name,age) select name,age user_02
1、update user_02 set name = ‘lisi’ where id = ‘2’
2、update user_02 set name = ‘lisi’ where id = ‘2’
结果:更新成功 结果:Deadlock found when trying to get lock; try restarting transaction

二、问题复原过程

1、建表,向表2插入数据

CREATE TABLE `user_01`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `user_01`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
);
INSERT INTO `user_02` VALUES (2, 'lisi', '15');

2、事务A开始执行

2.1 新建单元测试

MySQL死锁记录(INSERT INTO ... SELECT.... )_第1张图片

2.2 调用的方法有事务注解(保证在一个事务中被提交)

断点至:insertBySelect()方法执行完毕
MySQL死锁记录(INSERT INTO ... SELECT.... )_第2张图片

2.3 navicat中执行SQL,此时SQL在等待状态

update user_02 
set name = 'lisi' where id = '2'

2.4 将程序断点执行完毕

SQL立即死锁
MySQL死锁记录(INSERT INTO ... SELECT.... )_第3张图片

备注: 实际执行的SQL内容

MySQL死锁记录(INSERT INTO ... SELECT.... )_第4张图片

三、问题解决过程

1. 查询当前INNODB日志,包含死锁记录

SQL命令:show engine innodb status;

------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-03-18 23:06:40 0xfcc
# 事务1的信息
*** (1) TRANSACTION:
TRANSACTION 10026282, ACTIVE 4 sec starting index read
mysql tables in use 1, locked 1
# ACTIVE 4 :表示事务活动时间
# starting :当前事务运行的状态
# tables in use 1 : 有一个表被使用
# locked 1 :有一个表锁
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
# LOCK WAIT :事务正在等待锁
# 2 lock struct(s) :该事务的锁链表长度为2,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁以及 autoinc 锁等。
# heap size 1184 :为事务分配的锁堆内存大小
# 1 row lock(s) :行锁的数量
MySQL thread id 835727, OS thread handle 14684, query id 59561617 218.69.52.198 root updating
update user_02 set name = 'lisi' where id = '2'
# 上述:事务的线程信息,数据库IP地址和数据库名
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 5749 page no 4 n bits 72 index PRIMARY of table `han`.`user_02` trx id 10026379 lock_mode X locks rec but not gap waiting
# 事务1正在等待主键索引上的 X锁 但是没有 gap锁释放
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000098fd0e; asc       ;;
 2: len 7; hex 02000003b30be1; asc        ;;
 3: len 4; hex 6c697369; asc lisi;;
 4: len 2; hex 3135; asc 15;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 5749 page no 4 n bits 72 index PRIMARY of table `han`.`user_02` trx id 10026379 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000098fd0e; asc       ;;
 2: len 7; hex 02000003b30be1; asc        ;;
 3: len 4; hex 6c697369; asc lisi;;
 4: len 2; hex 3135; asc 15;;

结论: 事务B 尝试获取X锁

*** (2) TRANSACTION:
TRANSACTION 10026378, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1
MySQL thread id 841869, OS thread handle 12380, query id 59596235 218.69.52.198 root updating
update user_02 set name = 'wangwu' where id = '2'

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 5749 page no 4 n bits 72 index PRIMARY of table `han`.`user_02` trx id 10026378 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000098fd0e; asc       ;;
 2: len 7; hex 02000003b30be1; asc        ;;
 3: len 4; hex 6c697369; asc lisi;;
 4: len 2; hex 3135; asc 15;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 5749 page no 4 n bits 72 index PRIMARY of table `han`.`user_02` trx id 10026378 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000098fd0e; asc       ;;
 2: len 7; hex 02000003b30be1; asc        ;;
 3: len 4; hex 6c697369; asc lisi;;
 4: len 2; hex 3135; asc 15;;

●事务B 正在等待的更新意向排他锁,刚好正在事务A的怀里。
●事务A 持有间隙锁,正在等待更新意向排它锁。

2. 结论

按照如下顺序:
事务A(S锁)→ 事务B(X锁) → 事务A(X锁) :会导致死锁发生(由mysql死锁检测机制触发)
解决方案:将事务A的操作升级为X锁。
具体操作包括:
1)将事务A操作1进行升级:select …for update ,即升级为X锁(仅适用于for update)
2)加入分布式锁,将事务A、B隔离开来执行(一定程度降低效率,不建议使用,根据实际业务来决定)

3. select加锁分析

基础知识

锁类型
共享锁(S锁):假设事务T1对数据A加上共享锁,那么事务T2可以读数据A,不能修改数据A。
排他锁(X锁):假设事务T1对数据A加上共享锁,那么事务T2不能读数据A,不能修改数据A。
我们通过update、delete等语句加上的锁都是行级别的锁。只有LOCK TABLE … READ和LOCK TABLE … WRITE才能申请表级别的锁。
意向共享锁(IS锁):一个事务在获取(任何一行/或者全表)S锁之前,一定会先在所在的表上加IS锁。
意向排他锁(IX锁):一个事务在获取(任何一行/或者全表)X锁之前,一定会先在所在的表上加IX锁。

select * from table where id = ?;
执行的是快照读,读的是数据库记录的快照版本,是不加锁的。
select * from table where id = ? lock in share mode;
会对读取记录加S锁 (共享锁)
select * from table where id = ? for update
会对读取记录加X锁 (排他锁)

你可能感兴趣的:(mysql,mysql)