Innodb死锁分析-案例1

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sun_ashe/article/details/82589045

简介

本文是死锁分析一系列文章中的第一篇,是线上真实发生的死锁案例。以后会不定时更新此类文章,以加固对Innodb加锁机制的理解。

死锁日志

日志如下,数据已经脱敏处理。
死锁日志.png-441kB

从死锁日志中,不难发现
1,事务1 在对索引index_user_id插入时,加锁(插入意向锁)陷入等待状态
2,事务2 和事务1等待情况相同
3,事务2 拥有对heap_no=1的数据的next key lock

查看表结构,index index_user_id是唯一索引,并且索引字段为jdb_user_id,而此时,两条sql插入的为相同的唯一值。

假设
1,如果事务中只是包含insert语句
对于如上假设,两个对于相同唯一索引的插入,不会造成日志中的死锁情况
所以,可以肯定事务中包含其他的语句,然而通过show engine innodb status是无法看到的。

询问业务RD
拿到了正确的事务语句,场景变得清晰,模拟场景如下:
表结构:

mysql> show create table cc\G
*************************** 1. row ***************************
       Table: cc
Create Table: CREATE TABLE `cc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  `name` varchar(10) NOT NULL DEFAULT 'cc',
  PRIMARY KEY (`id`),
  UNIQUE KEY `age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

表中数据

mysql> select * from cc;
+----+------+------+
| id | age  | name |
+----+------+------+
|  1 |    5 | cc   |
|  9 |   15 | cc   |
+----+------+------+
2 rows in set (0.00 sec)

构建场景如下:

session-1 session-2
begin; begin
update cc set name=’a’ where age=20;
update cc set name=’a’ where age=20;
insert into cc(age,name) values(20,’a’)(waiting)
insert into cc(age,name) values(20,’a’)(触发死锁)
LATEST DETECTED DEADLOCK
------------------------
2018-09-10 13:59:18 0x700001ee9000
*** (1) TRANSACTION:
TRANSACTION 327537, ACTIVE 514 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1160, 2 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 123145334444032, query id 86 localhost root update
insert into cc(age,name) values(20,'a')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 184 page no 4 n bits 72 index age of table `test_lock`.`cc` trx id 327537 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 327538, ACTIVE 504 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1160, 2 row lock(s), undo log entries 1
MySQL thread id 4, OS thread handle 123145334722560, query id 87 localhost root update
insert into cc(age,name) values(20,'a')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 184 page no 4 n bits 72 index age of table `test_lock`.`cc` trx id 327538 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 184 page no 4 n bits 72 index age of table `test_lock`.`cc` trx id 327538 lock_mode X insert intention waiting
Record 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 (2)

死锁原因

事务1,执行update语句,由于当前并没有age=20的数据,所以加锁情况为当前数据页的最大记录的next key lock。

2018-09-10T13:50:44.542896+08:00 3 [Note] InnoDB: trx_id: 327537 create a record lock and add it to lock hash table,
space_id: 184
page_no: 4
heap_no: 1         //当前索引page的最大记录,
n_bits: 72
primary key: 0
is record lock: 1
is waiting: 0
is gap: 0
is record not gap: 0
is insert intention: 0
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

事务2执行update之后,与事务1相同,这里需要注意的是,supremum这行记录的next key lock,在不同事务之间是不冲突的,这与真实的用户记录锁不太一样。

而当事务1/2执行插入语句时,需要获取supremum这行记录的next key lock并且带有插入意向属性,如下:

^@2018-09-10T13:59:02.168195+08:00 3 [Note] InnoDB: trx_id: 327537 create a record lock and add it to lock hash table,
space_id: 184
page_no: 4
heap_no: 1
n_bits: 72
primary key: 0
is record lock: 1
is waiting: 1
is gap: 0
is record not gap: 0
is insert intention: 1
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

而插入意向锁和gap锁是不兼容的,所以形成了互相等待,导致死锁。

猜你喜欢

转载自blog.csdn.net/sun_ashe/article/details/82589045