Deadlock case five

Source: public account yangyidba


I. Introduction

Deadlock is actually a very interesting and challenging technical problem. Probably every DBA and some developers will encounter it in the process of work. Regarding deadlock, I will continue to write a series of case studies, hoping to help friends who want to understand deadlock. This article is derived from a deadlock case in the production process.

2. Background knowledge

The official document [1] stated:

"REPLACE is done like an INSERT if there is no collision on a unique key. Otherwise, an exclusive next-key lock is placed on the row to be replaced."

"If there is no unique key conflict, the replace operation and insert lock are the same. But if there is a unique key conflict, the system will add LOCK X next-key lock to the record when the replace statement is executed."

If you think the above translation is relatively simple, just take a look at the following introduction[2]

create table t1(
a int auto_increment primary key,
b int,
c int,
unique key (b));
replace into t1(b,c) values (2,3)

Step 1 Normal insertion logic

First insert the clustered index. In the above example, column a is an auto-increment column. Since it is not explicitly specified, a non-conflicting new value will be generated before each Insert.

Then insert the secondary index b, because it is the only index, when checking the duplicate key, add the record lock, the type is LOCK_X

For ordinary INSERT operations, when the duplicate key needs to be checked, add LOCK_S lock, and for Replace into or INSERT..ON DUPLICATE operations, add LOCK_X record lock. When the record already exists, the error DB_DUPLICATE_KEY is returned.

Step 2 handle errors

Since the duplicate key was detected in the previous step, the clustered index record inserted in the first step needs to be rolled back.

Step 3 Conversion operation

After failing to return from the InnoDB layer to the Server layer and receiving a duplicate key error, first retrieve the index with the unique key conflict, and lock the conflicting index record (and clustered index record)

Then confirm the conversion mode to resolve the conflict:

If the uk conflicting index is the last unique index, there is no foreign key reference, and there is no delete trigger, use UPDATE ROW to resolve the conflict

Otherwise, use DELETE ROW + INSERT ROW to resolve the conflict. If it is a primary key conflict, it will be deleted first.

Step 4 Update records

In this example, a is the primary key. Updates to the clustered index and the secondary index are all marked deletion + insertion of new records. For clustered indexes, due to changes in the PK column, use delete + insert clustered index records to update. For the secondary unique key index, the same way of marking deletion + insertion is adopted.

Three, case analysis

3.1 Prepare the test environment

Transaction isolation level REPEATABLE READ

data preparation

create table ix(id int not null auto_increment,
a int not null ,
b int not null ,
primary key(id),
idxa(a)
) engine=innodb default charset=utf8;
insert into ix(a,b) valuses(1,1),(5,10),(15,12);

Deadlock scenario

3.2 Process analysis

After executing a statement each time, execute show innodb engine status to view the status of the transaction,

The transaction log for executing replace into ix(a,b) values(5,8) is as follows

---TRANSACTION 1872, ACTIVE 46 sec
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 1156, OS thread handle 0672, query id 114 localhost msandbox

analysis

replace into ix(a,b) values(5,8), because the record a=5 already exists, the record will be updated, and the record will be added with Next Key lock RECORD lock, GAP lock,

The transaction generates 2 undos, holding 4 locks and an IX lock, 1 row lock for a = 5 row, and 2 gap locks a between 1-5 and 5-15.

The transaction log for executing replace into ix(a,b) values(8,10) is as follows

---TRANSACTION 1873, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s),
undo log entries 1
MySQL thread id 1155, OS thread handle 3008,
query id 117 localhost msandbox update
replace into ix(a,b) values(8,10)
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 80 
index idx_a of table `test`.`ix` trx id 1873 
lock_mode X locks gap before rec insert intention waiting
---TRANSACTION 1872, ACTIVE 69 sec
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2

analysis

There is no record of a=8 in the table, so it is similar to insert into ix(a,b) values(8,10). But a=8 conflicts with the gap lock [5-15] held by sess1, so it waits for lock_mode X locks gap before rec insert intention waiting, and enters the waiting queue. This lock is held by sess1.

Execute replace into ix(a,b) values(9,12); the transaction log executes the statement as follows : sess2 immediately reports a deadlock

*** (1) TRANSACTION:
TRANSACTION 1866, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 1155, OS thread handle 3008, query id 101 localhost msandbox update
replace into ix(a,b) values(8,10)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 80 index idx_a of table `test`.`ix` trx id 1866 
lock_mode X locks gap before rec insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 1865, ACTIVE 19 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 5 row lock(s),
undo log entries 3
MySQL thread id 1156, OS thread handle 0672,
query id 102 localhost msandbox update
replace into ix(a,b) values(9,12)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 24 page no 4 n bits 80 index idx_a of table `test`.`ix` trx id 1865 lock_mode X
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 80 
index idx_a of table `test`.`ix` trx id 1865 
lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (1)

Log analysis

  1. replace into ix(a,b) values(9,12); Similar to insert(8,10), you need to apply for lock_mode X locks gap before rec insert intention waiting, and enter the lock request queue to wait.

  2. Transaction T2 replace into ix(a,b) values(5,8); This statement holds 4 locks and one IX lock, 1 row lock for a=5 row, and 2 a in 1-5,5- GAP lock between 15.

  3. Transaction T1 replace into ix(a,b) values(8,10); a=8 conflicts with the gap lock [5,15] held by sess1, so it waits for lock_mode X locks gap before rec insert intention waiting, and enters the waiting queue inside.

  4. Transaction T2 replace into ix(a,b) values(9,12), a=9 is also between [5-15], need to wait for T1's insert intention lock to be released, T1 waits for T2(SQL1), T2(SQL2) Waiting for T1 to lead to deadlock, the system chooses to roll back transaction T1.

Four, summary

Analysis and positioning of the problem, how to solve it? The current advice for development is to avoid using the replace into method, use a single select check + insert method, or if a certain deadlock can be accepted, you can reduce concurrent execution and change to serial. Interested friends can reproduce themselves, have better solutions, and communicate with each other.

Five, reference

[1] https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html explains how to lock various statements. Students who are interested in deadlock must not miss it.

[2] http://mysqllover.com/?p=1312

This article is transferred from teacher Yang Qilong's public account (yangyidba). He has been focusing on database technology and performance optimization, failure case analysis, database operation and maintenance technical knowledge sharing, personal growth and self-management and other topics for a long time. Welcome to scan the code for attention.

Extended reading

The full text is over.

Enjoy MySQL :)

Zhishutang's new course K8S is online

Scan the code to start a new learning journey

Guess you like

Origin blog.csdn.net/n88Lpo/article/details/108655883