a deadlock case

Problem Description

Today's online consumer has a deadlock problem caused by an insert. Here, a DEMO is used to reproduce the entire process of the case and analyze it in detail.

The table structure is as follows:

mysql> show create table test_table;
| Table      | Create Table                                                                                
| test_table | CREATE TABLE `test_table` (
  `id` int NOT NULL AUTO_INCREMENT,
  `a` int NOT NULL,
  `b` int DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `test_table_a_uindex` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |

1 row in set (0.00 sec)

Table data:

mysql> select * from test_table;
+----+----+------+
| id | a  | b    |
+----+----+------+
|  1 |  1 |    1 |
| 20 | 20 |   20 |
| 50 | 50 |   50 |
+----+----+------+
3 rows in set (0.02 sec)

The code in the transaction is to update first, and then insert if the record does not exist.

Transaction 1:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test_table set b = 1 where a = 30;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

Transaction 1 starts the transaction and updates a record that does not exist (at this time, a gap lock will be added to a, [20,50]).

Transaction 2:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test_table set b = 1 where a = 31;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

Transaction 2 starts the transaction and updates a record that does not exist (at this time, a, [20, 50 will also add a gap lock]).

Transaction 1:

mysql> insert into test_table values(30,30,1);
mysql> waiting...

Transaction 1 inserts a record, which will be blocked at this time...

Transaction 2:

ysql> insert into test_table values(31,31,1);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Transaction 2 also inserts a record. At this time, an error will be reported, deadlock, and the transaction will be rolled back.

Transaction 1:

mysql> insert into test_table values(30,30,1);
Query OK, 1 row affected (12.58 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

After transaction 2 is rolled back, transaction 1 is successfully inserted and committed successfully.

problem analysis

1. An update operation is performed in transaction 1 and transaction 2 respectively, and the records of the operation do not exist. At this time, transaction 1 and transaction 2 respectively add a gap lock to the range of a and [20,50].

Note: Gap locks are compatible with gap locks (shared locks)

2. An insert operation is performed in transaction 1. At this time, because transaction 2 adds a gap lock to the range of a, [20,50], the insert operation of transaction 1 is blocked.

3. An insert operation was also performed in transaction 2, which was also blocked by the gap lock of transaction 1. At this time, transaction 1 is waiting for transaction 2 to release the gap lock, and transaction 2 is also waiting for transaction 1 to release the gap lock, forming a deadlock, so transaction 2 reports an error "deadlock" and rolls back the transaction.

4. After transaction 2 is rolled back, the gap lock of transaction 2 is released, the insert operation of transaction 1 is executed successfully, and transaction 1 is submitted successfully.

solution

Two solutions were considered:

Option One

The insert operation in the transaction is mentioned before the transaction, and each time the transaction starts, select it. If the record does not exist, insert an empty record into it, and only need to perform the update operation in the transaction.

Disadvantage: One more select operation may affect the performance of the interface, and the pressure test judgment needs to be re-performed.

Option II

Lower the mysql transaction isolation level from RR to RC.

Disadvantages: there is a problem of phantom reading

Comprehensive consideration, since this cluster is only used for the storage of pipeline and statistical data, the second solution is adopted : lower the transaction isolation level .

knowledge expansion

Is there a phantom read problem in the RR isolation level in InnoDB?

Before answering this question, let me assume that you know that the definition of the database isolation level is all for "current read" .

First, let's look at a section of InnoDB's official documentation:

For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key locks to block insertions by other sessions into the gaps covered by the range.

The general meaning is that at the RR level, if the query condition can use a unique index, or a unique query condition, then only row locks are added. If it is a range query, then a gap lock or next will be added to this range -key lock (row lock + gap lock).

InnoDB's RR isolation domain will add GAP to the range, and there will be no phantom reading.

summary

  • The transaction is best not to be too long, otherwise problems such as lock waiting and deadlock will easily occur
  • It is best not to put the insert operation into a transaction, otherwise it is easy to cause a deadlock problem (waiting for each other).

Guess you like

Origin blog.csdn.net/finish_dream/article/details/120340738