在线上发布新功能后,追踪了日志记录,发现有死锁的报错记录。因业务复杂,仅简化问题,先将问题简化复原。特在此记录解决分析过程。
先抛出问题,后附解决方案;
事务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 |
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');
update user_02
set name = 'lisi' where id = '2'
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 持有间隙锁,正在等待更新意向排它锁。
按照如下顺序:
事务A(S锁)→ 事务B(X锁) → 事务A(X锁) :会导致死锁发生(由mysql死锁检测机制触发)
解决方案:将事务A的操作升级为X锁。
具体操作包括:
1)将事务A操作1进行升级:select …for update ,即升级为X锁(仅适用于for update)
2)加入分布式锁,将事务A、B隔离开来执行(一定程度降低效率,不建议使用,根据实际业务来决定)
基础知识
锁类型
共享锁(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锁 (排他锁)