在线上发布新功能后,追踪了日志记录,发现有死锁的报错记录。因业务复杂,仅简化问题,先将问题简化复原。特在此记录解决分析过程。
先抛出问题,后附解决方案;
| 事务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');2、事务A开始执行 2.1 新建单元测试 2.2 调用的方法有事务注解(保证在一个事务中被提交)
断点至:insertBySelect()方法执行完毕
update user_02 set name = 'lisi' where id = '2'2.4 将程序断点执行完毕
SQL立即死锁
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锁 (排他锁)



