Source: public account yangyidba
A , foreword
deadlock, in fact, is a very interesting technical problem is also very challenging, probably each part of the development and DBA students will meet in the course of their work. Regarding deadlock, I will continue to write a series of case studies, hoping to help friends who want to understand deadlock. This article introduces an example of deadlock caused by three concurrent inserts. The root cause is that the unique key of the insert applies for the special GAP lock of the insertion intention lock. In fact, it is more reasonable to call Insert Intention Gap Lock.
2. Case analysis
2.1 Environment preparation
Percona server 5.6 RR mode
CREATE TABLE `t6` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
unique KEY `idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
insert into t6 values(1,2),(2,8),(3,9),(4,11),(5,19)
sess1 |
sess2 |
sess3 |
begin; |
||
insert into t6(id,a) values(6,15); |
begin; |
|
insert into t6(id,a) values(7,15); |
begin; |
|
insert into t6(id,a) values(8,15); |
||
rollback; |
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
2.2 Deadlock log
------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-09-18 10:03:50 7f78eae30700
*** (1) TRANSACTION:
TRANSACTION 462308725, ACTIVE 18 sec inserting, thread declared inside InnoDB 1
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 3825465, OS thread handle 0x7f78eaef4700, query id 781148519 localhost root update
insert into t6(id,a) values(7,15)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308725 lock_mode X insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 462308726, ACTIVE 10 sec inserting, thread declared inside InnoDB 1
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 3825581, OS thread handle 0x7f78eae30700, query id 781148528 localhost root update
insert into t6(id,a) values(8,15)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308726 lock mode S
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308726 lock_mode X insert intention waiting
*** WE ROLL BACK TRANSACTION (2)
2.3 Deadlock analysis
First of all, we still need to re-emphasize the locking logic of insert operation.
The first stage: uniqueness constraint check, first apply for LOCK_S + LOCK_ORDINARY.
Second stage: acquire the lock of stage one and insert successfully, the insert position has a gap lock: LOCK_INSERT_INTENTION, in order to prevent other insert unique key conflicts.
New data insertion: LOCK_X + LOCK_REC_NOT_GAP
For the insert operation, if a unique constraint conflict occurs, you need to add S Next-key Lock to the conflicting unique index. From here, you will find that even at the RC transaction isolation level, there will also be a Next-Key Lock lock, thereby blocking concurrency. However, what the document does not say is that for the unique index where the conflict is detected, the waiting thread needs to lock the next record after obtaining the S Lock. The source code is judged by the function row_ins_scan_sec_index_for_duplicate.
Secondly, we need the unlocked compatibility matrix.
From the compatibility matrix, we can draw the following conclusions:
There will be no conflicts between INSERT operations.
GAP, Next-Key will block Insert.
GAP and Record, Next-Key will not conflict
Record and Record, Next-Key conflict with each other.
The existing Insert lock does not prevent any locks to be added.
In this case, three sessions are executed concurrently. I plan to analyze the transaction log after each step is executed step by step.
The first step, sess1 performs the insert operation
insert into t6(id,a) values(6,15);
---TRANSACTION 462308737, ACTIVE 5 sec
1 lock struct(s), heap size 360, 0 row lock(s), undo log entries 1
MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149440 localhost root init
show engine innodb status
TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX
Because of the first inserted statement, the uniqueness conflict check passed and the insertion was successful (6,15). At this time, the sess1 session holds the LOCK_X|LOCK_REC_NOT_GAP lock of (6,15). Refer to "INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row." The
second step, sess2 performs an insert operation
insert into t6(id,a) values(7,15);
---TRANSACTION 462308738, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825768, OS thread handle 0x7f78ea9c9700, query id 781149521 localhost root update
insert into t6(id,a) values(7,15)
------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting
------------------
TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting
---TRANSACTION 462308737, ACTIVE 66 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149526 localhost root init
show engine innodb status
TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308737 lock_mode X locks rec but not gap
First, sess2's insert applies for an IX lock, because the sess1 session has been inserted successfully and holds the X row lock with the unique key a=15, so sess2 insert performs the uniqueness check, first apply for LOCK_S + LOCK_ORDINARY, and the transaction log list prompts lock mode S waiting
The third step, sess3 performs an insert operation
insert into t6(id,a) values(8,15);
---TRANSACTION 462308739, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825764, OS thread handle 0x7f78ea593700, query id 781149555 localhost root update
insert into t6(id,a) values(8,15)
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308739 lock mode S waiting
------------------
TABLE LOCK table `test`.`t6` trx id 462308739 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308739 lock mode S waiting
---TRANSACTION 462308738, ACTIVE 35 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825768, OS thread handle 0x7f78ea9c9700, query id 781149521 localhost root update
insert into t6(id,a) values(7,15)
------- TRX HAS BEEN WAITING 35 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting
------------------
TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting
---TRANSACTION 462308737, ACTIVE 97 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149560 localhost root init
show engine innodb status
TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308737 lock_mode X locks rec but not gap
It is consistent with the lock application process of session sess2, all of which are waiting for sess1 to release the lock resources.
The fourth step sess1 performs a rollback operation, sess2 does not commit
sess1 rollback;
At this time, sess2 is inserted successfully and sess3 is deadlocked. At this time, sess2 insert is inserted successfully and has not yet been submitted. The transaction list is as follows:
------------
TRANSACTIONS
------------
Trx id counter 462308744
Purge done for trx s n:o < 462308744 undo n:o < 0 state: running but idle
History list length 1866
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 462308737, not started
MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149626 localhost root init
show engine innodb status
---TRANSACTION 462308739, not started
MySQL thread id 3825764, OS thread handle 0x7f78ea593700, query id 781149555 localhost root cleaning up
---TRANSACTION 462308738, ACTIVE 75 sec
5 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1
MySQL thread id 3825768, OS thread handle 0x7f78eadce700, query id 781149608 localhost root cleaning up
TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock_mode X insert intention
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S locks gap before rec
Three, the cause of deadlock
sess1 insert is successful and X lock is added to the unique key of a=15.
sess2 executes insert (6,15), and performs uniqueness check before inserting. It is found that the duplicate key of the inserted record of sess1 needs to apply for LOCK_S|LOCK_ORDINARY, but it conflicts with sess1's (LOCK_X | LOCK_REC_NOT_GAP), join the waiting queue and wait for sess1 Release the lock.
sess3 executes insert (7,15), and performs uniqueness check before inserting. It is found that the duplicate key with the inserted record of sess1 needs to apply for LOCK_S|LOCK_ORDINARY, but it conflicts with (LOCK_X | LOCK_REC_NOT_GAP) of sess1, join the waiting queue and wait for sess1 Release the lock.
sess1 executes a rollback, sess1 releases the exclusive record lock (LOCK_X | LOCK_REC_NOT_GAP) on index a=15, and then sess2 and sess3 obtain the S lock (LOCK_S|LOCK_ORDINARY) successfully, sess2 and sess3 both request the exclusive record lock on index a=15 (LOCK_X | LOCK_REC_NOT_GAP), the log prompts lock_mode X insert intention. Since X lock and S lock are mutually exclusive, sess2 and sess3 both wait for each other to release S lock, so a deadlock occurs, and MySQL chooses to roll back one of them.
Four, summary
Deadlock analysis is already very challenging, especially for insert unique key conflicts, which need to be applied in multiple stages and understand the compatibility matrix of locks. For this piece of knowledge I need to learn and understand, this article can be regarded as an introduction. If there is any incorrect analysis and understanding, please correct me.
Extended reading
The full text is over.
Enjoy MySQL :)
Teacher Ye's "MySQL Core Optimization" class has been upgraded to MySQL 8.0, scan the code to start the journey of MySQL 8.0 practice