If you want to implement a large database application system with high throughput and high reliability; if you want to migrate from other databases to MySQL; if you want to tune MySQL performance; then it is very useful to learn and master the lock and transaction model of InnoDB.
Article Directory
-
-
- 14.7.1 Locks in InnoDB
- 14.7.2 InnoDB Transaction Model
- 14.7.3 Locks set by different SQL statements in InnoDB
- 14.7.4 Phantom rows
- 14.7.5 Deadlocks in InnoDB
- Related Links
-
14.7.1 Locks in InnoDB
This section describes the various types of locks in InnoDB.
Friends passing by, please help me to support with Star: https://github.com/cncounter/translation/
Shared locks and exclusive locks
InnoDB implements standard row-level locking, including two types: shared lock (S lock for short) and exclusive lock (X lock for short). [Exclusive lock] is sometimes also called [mutual exclusion lock].
- Shared lock (S): Allows the transaction holding the lock to read this row.
- Exclusive lock (X): Allows the transaction holding the lock to update or delete this row.
If a transaction T1
holds r
a shared lock ( S
) on row , then another transaction T2
requests r
a lock on row , as follows:
- If a lock
T2
is requestedS
, it can be granted immediately. The result is:T1
andT2
both hold the lockr
on rowS
. - If a lock
T2
is requestedX
, it cannot be granted immediately and needs to be queued to wait.
If a transaction holds an exclusive lock ( ) T1
on row , any request for a lock on row by another transaction cannot be granted immediately. At this point, the transaction must wait for the transaction to release the lock on the row.r
X
T2
r
T2
T1
r
intent lock
InnoDB supports multi-granularity locks ( multiple granularity locking
), allowing row locks and table locks to coexist.
For example, LOCK TABLES ... WRITE
the statement sets an exclusive lock (lock) on the specified table X
.
In order to make locks at multiple granularity levels practical, InnoDB uses [Intent Lock] ( intention lock
), or [Intent Lock].
The intention lock is a table-level lock (table-level lock), indicating which type of lock the current transaction will later perform on the rows in the table (whether you want a shared lock or an exclusive lock).
There are two types of intent locks:
共享意向锁
(IS
,intention shared lock
): Indicates that the transaction intends to set shared locks on certain rows in the table.排他意向锁
(IX
,intention exclusive lock
): Indicates that the transaction intends to set exclusive locks on certain rows in the table.
For example, a lock SELECT ... LOCK IN SHARE MODE
is set IS
and a lock SELECT ... FOR UPDATE
is set IX
.
The protocol of the intent lock is as follows:
- Before a transaction can acquire a shared lock on a row in a table, it must first acquire
IS
a lock on that table, or a more restrictive lock. - Before a transaction can acquire an exclusive lock on a row in a table, it must first acquire
IX
a lock on that table.
The compatibility of intent locks with other locks is summarized as follows:
X |
IX |
S |
IS |
|
---|---|---|---|---|
X |
conflict | conflict | conflict | conflict |
IX |
conflict | 兼容 |
conflict | 兼容 |
S |
conflict | conflict | 兼容 |
兼容 |
IS |
conflict | 兼容 |
兼容 |
兼容 |
If the requested lock is compatible with an existing lock, the requested transaction is granted immediately;
but if it conflicts with an existing lock, the lock is not granted. The requesting transaction needs to wait until the conflicting lock is released.
If a lock request conflicts with an existing lock and cannot be granted because it would cause a "deadlock", an error will be reported directly.
LOCK TABLES ... WRITE
Intent locks do not block any other requests except (for example) full table requests . The main purpose of an intent lock is to indicate that a transaction is locking a row in a table, or is about to lock a row in a table.
In SHOW ENGINE INNODB STATUS
the statement and InnoDB monitor
output transaction information, the intent lock looks like this:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
record lock
Record lock (Record Lock), which is also a lock on the index record (index record). For example, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
the statement prevents other transactions from inserting/updating/or deleting rows t.c1
with a value of 10
.
A record lock always locks an index record, even for tables with no indexes defined. For tables that do not have an index set, InnoDB will automatically create a hidden clustered index ( clustered index
, also known as a clustered index), and use this index to perform record locks. See Section 14.6.2.1, “Clustered and Secondary Indexes” for details .
In SHOW ENGINE INNODB STATUS
statement and InnoDB monitor
output transaction information, record locks look like this:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc ''O;;
2: len 7; hex b60000019d0110; asc ;;
gap lock
Gap Lock:
- is to lock the gap between index records,
- Or lock the gap before the first index record,
- Or lock the gap after the last index record.
For example, SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
other transactions are prevented from 15
inserting this value into t.c1
the column, regardless of whether the value exists in the column, because the gaps between all values in this range are locked.
A gap may span a single index value, multiple index values, or even be empty.
Gap locks are a trade-off between performance and concurrency, and are only used in certain transaction isolation levels.
For statements that use unique indexes to lock unique rows, gap locks are not required. (If it is a unique index composed of multiple columns, and only some columns are included in the search condition, a gap lock will also be generated.)
For example, if id
the column has a unique index, the following SQL statement will only id = 100
use the record lock for the row, Regardless of whether other sessions insert newlines in the preceding gap:
SELECT * FROM child WHERE id = 100;
If id
the column does not have an index, or is not a unique index, the statement will lock the previous gap together.
It is worth noting that different transactions can hold conflicting locks on the same gap. For example, transaction A can hold a shared gap lock (gap S-lock) on a gap, while transaction B can hold an exclusive gap lock (gap X-lock) on the same gap. The reason conflicting gap locks are allowed is that if a record is purged from the index, gap locks held by different transactions on that record must be merged.
Gap locks in InnoDB are "purely inhibitory", the sole purpose of which is to prevent other transactions from inserting into the gap. Gap locks can co-exist. A gap lock held by one transaction does not prevent other transactions from locking the same gap. There is also no difference between shared and exclusive gap locks. They do not conflict with each other and have the same effect.
Gap locks can be explicitly disabled. This happens when the transaction isolation level is set to READ COMMITTED
, or the system variable is enabled (obsolete). innodb_locks_unsafe_for_binlog
Gap locks are disabled during searches and index scans, and are only used for foreign key constraint checks and duplicate key checks.
There are other effects when using READ COMMITTED
isolation-level or enabling . innodb_locks_unsafe_for_binlog
MySQL WHERE
will immediately release the record locks of unmatched rows after the condition calculation is completed. For UPDATE
the statement, InnoDB performs a "semi-consistent read (semi-consistent)", returning the latest committed version to MySQL so that MySQL can determine whether the row matches the condition UPDATE
of WHERE
.
Pro key lock
Next-Key Lock is a combination of index record locks and previous gap locks.
The way InnoDB row-level locks are executed is that when searching or scanning indexes, shared locks or mutex locks are set on the index records encountered. Therefore, row-level locks are essentially index record locks. A temporary key lock on an index record also affects the "gap" preceding that index record. That is: Pro key lock = record lock + gap lock.
If a session (session) R
holds a shared temporary key lock or an exclusive temporary key lock on an index record, other sessions cannot R
insert new index records in the previous gap according to the sorting direction of the index.
Suppose an index contains 10
, 11
, 13
, and 20
these 4 values; at this time, the key lock of the index can include these intervals (interval):
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
Among them, parentheses indicate open intervals, excluding endpoints; square brackets indicate closed intervals, including endpoints.
The last group of intervals, the range of the key lock is: greater than the current maximum value, until positive infinity (positive infinity). Infinity does not contain real index records, in fact, this temporary key lock only locks the gap after the current maximum index value.
By default, InnoDB REPEATABLE READ
runs under the transaction isolation level. At this isolation level, InnoDB uses key locks for searches and index scans to prevent phantom rows.
In the transaction information output by SHOW ENGINE INNODB STATUS
the statement and the temporary key lock looks like this:InnoDB monitor
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 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;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc ''O;;
2: len 7; hex b60000019d0110; asc ;;
insert intent lock
INSERT
Insert Intention Lock (Insert Intention Lock) is a gap lock set by the operation before inserting a new row . This lock indicates the intention signal of insertion, and the execution method is: if multiple transactions want to insert records in the same gap, as long as they are not in the same position, there is no need to block or wait.
For example, there are 2 values in the index as 4
and 7
. Suppose there are two transactions, when inserting 5
and 6
respectively, before acquiring the exclusive lock of the row to be inserted, each transaction uses the insertion intent lock to lock the gap between 4
and 7
, but they will not block each other, because the specific row is not conflicting.
The following example demonstrates that a transaction obtains an insert intent lock before obtaining an exclusive lock for inserting records. This example involves two clients: A and B.
A
The client creates a table containing two index records ( 90
and 102
), then starts a transaction, and places an exclusive lock on the index record with an ID greater than 100. The exclusive lock includes a gap lock before record 102:
# A客户端
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
After client B starts the transaction, it tries to insert record 101 into the gap. While waiting to acquire an exclusive lock, the insert intent lock will be acquired first.
# B客户端
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
In SHOW ENGINE INNODB STATUS
the statement and InnoDB monitor
output transaction information, insert an intent lock like this:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc "" ;;
2: len 7; hex 9000000172011c; asc r ;;...
self-increasing lock
Self-increasing lock ( AUTO-INC
Lock) is a special table-level lock, AUTO_INCREMENT
a table with columns, acquired by a transaction that needs to insert a record. In the simplest scenario, if a transaction is inserting values into the table, other transactions must wait for him to complete before inserting new values into the table, so that the rows inserted by the first transaction get continuous auto-increment primary key values .
innodb_autoinc_lock_mode
Option, used to control the self-increasing algorithm. Allow us to weigh ourselves, whether to choose a predictable auto-increment sequence, or choose the high concurrency performance of the insert operation.
For more information, please refer to: Section 14.6.1.6, “AUTO_INCREMENT Handling in InnoDB” .
Predicate locks used by spatial indexes
InnoDB supports indexes on geospatial columns SPATIAL
. Please refer to Section 11.4.8, “Optimizing Spatial Analysis” for details .
When locking index records, temporary key locks do not support or transaction isolation levels SPATIAL
well . Because there is no absolute sort order in cubes, there is no way to tell who is the "next" key.REPEATABLE READ
SERIALIZABLE
In order to support tables with indexes in the transaction isolation level SPATIAL
, InnoDB uses predicate locks (Predicate Lock).
SPATIAL
Index records contain MBR values (minimum bounding rectangle, minimum bounding rectangle), so InnoDB sets predicate locks on index records that match MBR values to enforce consistent reads on the index. Other transactions cannot insert or modify rows matching the query criteria.
14.7.2 InnoDB Transaction Model
InnoDB's transaction model aims to combine the best properties of a multi-versioning database with traditional two-phase locking.
By default, InnoDB uses row-level locks and executes queries in a nonlocking consistent read manner, similar to an Oracle database.
Lock information in InnoDB is stored in a space-saving manner, so no lock escalation is required.
Supports multiple users locking each row in an InnoDB table, or any number of rows, without running out of InnoDB memory.
14.7.2.1 Transaction isolation levels
Transaction isolation is a fundamental feature of databases. Isolation is ACID
medium I
; the isolation level is a configurable item used to adjust performance, reliability, consistency, and reproducibility when multiple transactions are concurrently modified and concurrently queried ) balance between.
InnoDB supports four transaction isolation levels defined in the "SQL:1992 Standard":
READ UNCOMMITTED
(read uncommitted),READ COMMITTED
(read committed),REPEATABLE READ
(repeatable read), InnoDB's default isolation level.SERIALIZABLE
(serialization).
Users can change the isolation level of the current session (control the visibility of their own session), and also change the isolation level of all subsequent connections, SET TRANSACTION
just use the statement.
To set the server's default isolation level, use --transaction-isolation
the option on the command line or in a configuration file. For details on setting the isolation level, refer to Section 13.3.6, “SET TRANSACTION Statement” .
InnoDB uses different locking strategies for each transaction isolation level.
- The default
REPEATABLE READ
level can be used to achieve consistency, such asACID
critical data processing where the specification is important. - In scenarios such as batch reporting, you can use
READ COMMITTED
or evenREAD UNCOMMITTED
to relax consistency constraints. At this time, accurate consistency and repeatable results are relatively less important than reducing the overhead of locks. SERIALIZABLE
is more restrictive thanREPEATABLE READ
, and is mainly used in special cases, such asXA
transactions, or scenarios such as troubleshooting and troubleshooting concurrency and deadlock problems.
The various transaction isolation levels of MySQL are described in detail below. We cover the most commonly used isolation levels first and the least used isolation levels last.
REPEATABLE READ
[Repeatable Read] is the default isolation level of InnoDB. Repeatable read isolation level, consistent read in the same transaction, using the snapshot created on the first read.
This means that if multiple normal SELECT
statements (nonlocking) are executed in the same transaction, the consistency between these SELECT
statements can be guaranteed.
For details, see 14.7.2.3 Non-locking Consistent Read .
For UPDATE
statements, DELETE
statements, and locking reads (locking read, that is, SELECT ... FOR UPDATE
or SELECT ... LOCK IN SHARE MODE
statements), the lock used is determined according to whether the filter condition uses a unique index or uses a range condition:
-
For unique query conditions that use a unique index, InnoDB only locks the found index records, not the previous gaps.
-
For other query conditions, InnoDB will lock the scanned index range to prevent other sessions from inserting new values in this range through gap locks or key locks. For information about gap locks and adjacent key locks, please refer to the previous content: 14.7.1 Locks in InnoDB .
READ COMMITTED
Under the [read committed] isolation level, even within the same transaction, each consistent read will set and read its own new snapshot. For information on consistent reads, refer to 14.7.2.3 Non-locking consistent reads .
For locking read( SELECT ... FOR UPDATE
or SELECT ... LOCK IN SHARE MODE
) UPDATE
statements and DELETE
statements, InnoDB only locks the index records at this time, not the gaps between them, so other transactions can insert new records next to the locked records. At this time, gap locks are only used for foreign key constraint checks and duplicate key checks.
With gap locks disabled, there is a possibility of a phantom problem, as other sessions may insert new rows in the gap. For information on phantom reads, refer to 14.7.4 Phantom Rows .
READ COMMITTED
The isolation level only supports row-based bin-log. If used READ COMMITTED
together binlog_format=MIXED
with , the server automatically switches to a row-based bin-log.
Using READ COMMITTED
also has other effects:
For UPDATE
AND DELETE
statements, InnoDB only holds locks for rows that need to be updated or deleted. After MySQL has calculated WHERE
the condition, it releases the record locks for the unmatched rows. This greatly reduces the possibility of deadlocks, but it can still happen.
For UPDATE
the statement, if a row is locked, InnoDB will perform a semi-consistent read ("semi-consistent" read), return the latest committed version to MySQL, and let MySQL determine whether the row meets the UPDATE
conditions WHERE
. If the row matches (indicating an update is required), MySQL reads the row again, and this time InnoDB either locks it or waits for the above lock to be released first.
See the example below, let's start with this table:
CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;
In this case, because there is no index, the hidden clustered index will be used as a record lock during query and index scan.
Suppose a session A performs an update operation through the following statement:
# 会话A
START TRANSACTION;
UPDATE t SET b = 5 WHERE b = 3;
At this time, session A has not committed the transaction, and then the second session B performs the update operation through the following statement:
# 会话B
UPDATE t SET b = 4 WHERE b = 2;
When InnoDB executes UPDATE
, it will first set an exclusive lock (exclusive lock) for each row it reads, and then determine whether it needs to be modified. If InnoDB does not require modification, the lock on the row is released. Otherwise, InnoDB will hold the row lock until the end of the transaction. This affects the processing of transactions as shown below.
Assuming the default REPEATABLE READ
isolation level is used, the first one UPDATE
will first set an X lock on each row it scans and will not release any of them:
# REPEATABLE READ 隔离级别; 会话A
x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock
Because the first UPDATE
holds locks on all rows, the second UPDATE
blocks immediately trying to acquire any of the locks, and UPDATE
cannot proceed until after the first commits or rolls back:
# REPEATABLE READ 隔离级别; 会话B
x-lock(1,2); block and wait for first UPDATE to commit or roll back
If READ COMMITTED
isolation levels are used, the first UPDATE
acquires X locks on each row read by the scan, and then releases X locks on rows that do not need to be modified:
# READ COMMITTED 隔离级别; 会话A
x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)
For the second UPDATE
, InnoDB will perform a semi-consistent read ("semi-consistent" read), return the latest committed version to MySQL, and let MySQL determine whether the row meets the conditions UPDATE
of WHERE
:
# READ COMMITTED 隔离级别; 会话B
x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock
However, if WHERE
an indexed column is included in the condition, and InnoDB uses the index, only the indexed column is considered when acquiring and retaining the record lock.
In the following example, the first acquires and holds an X lock UPDATE
on all b = 2
rows.
The second UPDATE
will block when trying to acquire an X lock on the same record, because the index defined on the b column is also used.
CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);
COMMIT;
# 会话 A
START TRANSACTION;
UPDATE t SET b = 3 WHERE b = 2 AND c = 3;
# 会话 B
UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
Using READ COMMITTED
the isolation level innodb_locks_unsafe_for_binlog
is basically the same as setting the option [this option is obsolete], but there are some differences:
innodb_locks_unsafe_for_binlog
Is a global setting that affects all sessions, and the isolation level can be set globally for all sessions or individually for each session.innodb_locks_unsafe_for_binlog
Can only be set at server startup, while the isolation level can be set at startup or changed during runtime.
Therefore, READ COMMITTED
it innodb_locks_unsafe_for_binlog
is more convenient and flexible than .
READ UNCOMMITTED
[Read Uncommitted] Under the isolation level, SELECT
the statement is executed in a non-locking manner, but an earlier version of a row may be used. Therefore, when using this isolation level, the consistency of reading cannot be guaranteed. This phenomenon is called dirty read (dirty read). Otherwise, this isolation level is similar to READ COMMITTED
.
SERIALIZABLE
[Serialization] This isolation level is similar REPEATABLE READ
, if disabled autocommit
, InnoDB will implicitly SELECT
convert all ordinary statements to SELECT ... LOCK IN SHARE MODE
.
If autocommit ( autocommit
) is enabled, then SELECT
is alone in a transaction. Therefore considered read-only, serialization can be achieved without blocking other transactions if performed as a consistent non-locking read.
SELECT
Disable if you want to force ordinary statements to block and wait while other transactions modify the selected rows autocommit
.
14.7.2.2 autocommit, commit and rollback
In InnoDB, all user activities are performed within transactions. If autocommit mode ( autocommit
) is enabled, each SQL statement forms a transaction by itself.
Each new session connection in MySQL is automatically committed by default. If a SQL statement does not generate an error, it will be automatically committed after its execution.
If a certain SQL statement returns an error, it is decided whether to commit or rollback according to the specific error. For details, refer to Section 14.22.4, “InnoDB Error Handling” .
Sessions with autocommit enabled can also execute multi-statement transactions, beginning with an explicit START TRANSACTION
OR BEGIN
statement and ending with an COMMIT
OR ROLLBACK
statement. For details, please refer to Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Statements” .
If SET autocommit = 0
autocommit mode is disabled via , the session will always have an open transaction. COMMIT
or ROLLBACK
statement will end the current transaction and start a new one.
For a session with autocommit mode disabled, MySQL will perform a transaction rollback if the connection is disconnected or the session ends without explicitly committing the transaction.
Some statements end the transaction implicitly, as if a COMMIT
statement was automatically added before such a statement. For details, refer to Section 13.3.3, “Statements That Cause an Implicit Commit” .
COMMIT
Indicates that the changes made by the current transaction need to be made permanent and visible to other sessions. The statement ROLLBACK
cancels the modification in the current transaction. COMMIT
and ROLLBACK
both release all InnoDB locks set during the current transaction.
DML operation groups and transactions
The client connection of the MySQL database, the automatic submission mode is enabled by default, and each SQL statement will be automatically submitted after execution. Users who have used other database systems may not be used to this mode of operation, because their more common way is to execute a series of DML statements, and then submit or roll back together.
To use multi-statement transactions:
- The auto-commit mode can be turned off by the statement, and end the transaction with or
SET autocommit = 0
at the appropriate time .COMMIT
ROLLBACK
- In auto-commit state, you can
START TRANSACTION
start a transaction with and end withCOMMIT
orROLLBACK
.
The following examples demonstrate two transactions. Among them, the first transaction is committed; the second transaction is rolled back.
# 连接数据库
shell> mysql test
Executed SQL:
-- 建表
mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
Query OK, 0 rows affected (0.00 sec)
mysql> -- 在 autocommit 开启的状态下, 启动事务:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 关闭 autocommit 状态; 启动事务:
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO customer VALUES (20, 'Paul');
Query OK, 1 row affected (0.00 sec)
mysql> DELETE FROM customer WHERE b = 'Heikki';
Query OK, 1 row affected (0.00 sec)
mysql> -- 回滚事务:
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM customer;
+------+--------+
| a | b |
+------+--------+
| 10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>
Transactions in Client Programming Languages
In the MySQL client API, such as PHP, Perl DBI, JDBC, ODBC, or the standard C calling interface, transaction control statements (such as ) can be sent COMMIT
to the MySQL server as strings, just like ordinary SQL statements ( SELECT
and INSERT
). Certain APIs also separately provide functions/methods for committing and rolling back transactions.
14.7.2.3 Nonlocking Consistent Reads
Consistent read means that InnoDB presents a database snapshot at a certain point in time for a query through multi-version technology. The query can see the changes of all committed transactions before this point in time, but cannot see the changes made by new transactions or uncommitted transactions after this point in time.
The exception is that queries can see changes made by previously executed statements within the same transaction. This exception causes some exceptions: if you update some rows in the table, you will SELECT
see the latest version of the row since it was updated, but other rows may still see the old version. If other sessions also updated this table, this exception means that we may see some kind of state that does not exist.
If it is the default REPEATABLE READ
isolation level, all consistent reads in the same transaction will read the snapshot created when the transaction was first read. You can commit the current transaction, and then execute a new query statement to get the latest data snapshot.
When using READ COMMITTED
the isolation level, every consistent read in the transaction will set and read its own new snapshot.
Under READ COMMITTED
the and REPEATABLE READ
isolation levels, consistent read is SELECT
the default mode for InnoDB to process statements.
Consistent reads do not set any locks on the tables being read, so other sessions are free to perform modifications to those tables while reading.
Using the default REPEATABLE READ
isolation level, when performing ordinary SELECT
consistent reads, InnoDB will specify a point in time for the current transaction, and determine which data can be seen by all queries in the transaction based on this point in time. If after this given point in time, another transaction deletes a row of data and commits, then the current transaction does not see that this row has been deleted. Insert and update operations are handled in the same way.
hint
The snapshot of the database state is applicable to the statement in the transaction SELECT
, but not necessarily applicable to the DML statement (addition, deletion, modification). If some rows are inserted or modified, and the transaction is going to be committed later; then an OR statement executed
in another REPEATABLE READ
level of concurrent transaction may affect these just-committed rows, although that session may not be able to See these lines when reading. When a transaction updates or deletes rows committed by another transaction, those changes become visible to the current transaction.DELETE
UPDATE
For example the following scenario:
# 场景1
SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: 没有匹配的行. ::: 查不到
DELETE FROM t1 WHERE c1 = 'xyz';
-- 删除了被另一个事务提交的某些行... ::: 但确实会删除
# 场景2
SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: 没有匹配的行.::: 查不到
UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: 比如, 另一个事务 txn2 刚刚提交了 10 行 'abc' 值. ::: 但确实更新了...
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: 本事务 txn1 这时候可以看到刚刚更新的行.
SELECT
A transaction can be committed, and then another OR statement executed START TRANSACTION WITH CONSISTENT SNAPSHOT
to advance the point in time.
This is called multi-versioned concurrency control.
In the following example, only after Session B commits the transaction and Session A itself commits the transaction, A can see the new row of B, because at this time, the time point of A will be newer than the commit of B.
Session A Session B
SET autocommit=0; SET autocommit=0;
时间序
| SELECT * FROM t;
| empty set
| INSERT INTO t VALUES (1, 2);
|
v SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
---------------------
| 1 | 2 |
---------------------
If you want to view the latest state of the database (freshest), you can use READ COMMITTED
the isolation level, or use locking read:
SELECT * FROM t LOCK IN SHARE MODE;
When using READ COMMITTED
the isolation level, each consistent read within a transaction sets and reads its own new snapshot.
With LOCK IN SHARE MODE
a SELECT statement, a locking read: SELECT
may block until the end of the transaction containing the latest row. Please refer to 14.7.2.4 Locking Read .
Consistent read does not support certain DDL statements:
- Consistent read cannot
DROP TABLE
take effect at time, because MySQL cannot use the deleted table, and InnoDB has destroyed this table. - Consistent read cannot
ALTER TABLE
take effect on the operation, because this operation will create a temporary copy of the original table, and delete the original table after the temporary copy is built. When a consistent read is re-performed within a transaction, the data rows in the new table are not visible because these rows did not exist when the transaction took the snapshot. In this case, an error message will be returned:ER_TABLE_DEF_CHANGED
, “Table definition has changed, please retry transaction”.
The behavior differs for various queries that do not specify FOR UPDATE
or , such as , , and :LOCK IN SHARE MODE
INSERT INTO ... SELECT
UPDATE ... (SELECT)
CREATE TABLE ... SELECT
- By default, InnoDB uses stronger locks in these statements, while
SELECT
some behave likeREAD COMMITTED
each consistent read sets and reads its own new snapshot, even within the same transaction. - To perform non-locking reads in this case, enable
innodb_locks_unsafe_for_binlog
the option and set the transaction isolation level toREAD UNCOMMITTED
,READ COMMITTED
, orREPEATABLE READ
, to avoid locking while reading data rows.
14.7.2.4 LOCKED READ
If in a transaction, the data is queried first, and then the related data is inserted or updated, the conventional SELECT
statement does not provide sufficient protection.
Other transactions can update or delete the rows we just looked up.
InnoDB supports two types of locking reads (Locking Read), which can provide additional security:
-
SELECT ... LOCK IN SHARE MODE
Set shared locks on all rows read. Other sessions can read these rows, but none can be modified until the end of the current transaction. When querying, if some rows are modified by other transactions that have not yet been committed, the current query will be blocked until the end of those transactions, and then the latest values will be used.
-
SELECT ... FOR UPDATE
For index records found by the search, the data row and all associated index entries are locked as if the statement had been executed on those rows
UPDATE
. Other transactions will be blocked, including modification, useSELECT ... LOCK IN SHARE MODE
to read, and even read operations at certain isolation levels. Consistent reads ignore any locks set on records in the read view. (Because the old version of the data row cannot be locked, it is reconstructed through the memory copy of the record plus undo logs).
This type of clause is very useful when dealing with tree-structured or graph-structured data, whether it is a single table or multiple tables. We can traverse it first, and then modify some of the records.
When the transaction is committed or rolled back, the locks set by LOCK IN SHARE MODE
and will be released.FOR UPDATE
Notice
Locking reads are only possible if autocommit is disabled. (generally use
START TRANSACTION
the statement or settingautocommit=0
to disable autocommit)
When executing a nested statement query, the locked read in the outer query will not lock the data rows of the subquery, unless the subquery also specifies a locked read. For example, the following statement does not lock t2
rows in the table.
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;
To lock t2
rows in the table, a locking read is also required in the subquery:
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
Locking Read Usage Example
Example 1:
Suppose you need to child
insert a new row into the table, but make sure that parent
there is a corresponding record in the table. In application code, referential integrity can be ensured by the following sequence of operations.
First, the table is queried using a consistent read parent
to check if the parent record exists. Does this guarantee safe insertion of data into child
the table? No, because other sessions may delete the row of data in the table between SELECT
and without our knowledge .INSERT
parent
To avoid this potential bug, it can be LOCK IN SHARE MODE
executed by SELECT
:
SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;
After LOCK IN SHARE MODE
the query returns the parent record 'Jones'
, it is safe to add the record to child
the table and then commit the transaction. If other transactions try to acquire parent
the exclusive lock on the corresponding data row in the table, they will be blocked and need to wait for us to complete the operation before continuing, that is, we need to wait for the data in the two tables to be in a consistent state.
Example 2:
For another example, CHILD_CODES
there is an integer counter_field field in the table, which is used to child
assign a unique ID to each record in the table.
We cannot use consistent read or shared mode to read the current counter value, because then multiple clients will see the same value, and if two transactions try to add data with the same id, a duplicate key error will occur (duplicate-key error).
In this scenario, LOCK IN SHARE MODE
it is not a good solution. If multiple users read the counter at the same time, at least one of them will be stuck in a deadlock state when updating the counter.
To read a counter and increment it, FOR UPDATE
a locked read of the counter is performed before incrementing the counter. For example:
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
SELECT ... FOR UPDATE
The latest available data is read and an exclusive lock is set on each row read. Therefore, it sets UPDATE
the same lock as the statement sets.
This example is just to demonstrate SELECT ... FOR UPDATE
how works. In MySQL, the task of generating a unique identifier can actually be completed with one query:
UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();
SELECT
The statement will only query id information based on the current session. And without reading any of the tables.
14.7.3 Locks set by different SQL statements in InnoDB
Locking reads, UPDATE
and DELETE
statements, typically set record locks on scanned index records.
Regardless of whether the SQL statement contains WHERE
conditions or not, it will be locked. Because InnoDB does not record the exact WHERE condition, it only knows which index ranges it has scanned. Generally, a temporary key lock ( next-key lock
) is used, so that other transactions can be blocked (block) from inserting new rows into the previous gap (gap). Of course, it is possible to explicitly disable gap locks ( gap locking
), and then adjacent key locks will not be used. See the previous section 14.7.1 Locks in InnoDB for more information .
The transaction isolation level also affects the locks used; see Section 14.7.2.1, “Transaction Isolation Levels” .
If a secondary index is used in the search and an exclusive lock is to be set, InnoDB will also retrieve the corresponding clustered index record and lock it.
If no index is found, MySQL will perform a full table scan (scan the entire table), and each row in the table will be locked, thereby blocking all insertions into the table by other sessions. So it is very important to create a good index, so that you don't need to scan a lot of unnecessary rows when executing a query.
The locks set by InnoDB for various SQL statements are as follows:
-
SELECT ... FROM
, consistent read, the read is a snapshot, generally not locked; only the transaction isolation level isSERIALIZABLE
locked. ForSERIALIZABLE
level, a shared temporary key lock is set on the searched index records. However, for SQL statements that use unique indexes to query unique rows, only one index record lock needs to be set. -
For
SELECT ... FOR UPDATE
orSELECT ... LOCK IN SHARE MODE
, the scanned rows will be locked, but the rows that are not in the result set will generally be released immediately (such as not meeting theWHERE
filter conditions in the clause). But in some cases the row lock may not be released immediately, because during the execution of the query, the connection between the result row and its original data source may be lost. For example, inUNION
a statement, the rows scanned (and locked) in the table may be inserted into a temporary table before calculating whether they meet the result set. In this case, the relationship between the rows in the temporary table and the rows in the original table is lost, so the row locks are not released until after the query has finished executing. -
SELECT ... LOCK IN SHARE MODE
Sets a shared temporary key lock on index records encountered by the search. But if you are retrieving unique rows through a unique index, you only need to lock a single index record. -
SELECT ... FOR UPDATE
Sets an exclusive keylock on each record found in the search. The exceptions are statements that search for unique rows via a unique index, requiring only one index record to be locked.For the index records encountered by the search,
SELECT ... FOR UPDATE
it will block other session executionSELECT ... LOCK IN SHARE MODE
, and block certain isolation level transactions from reading data. A consistent read will ignore any locks set on the record in the read view. -
UPDATE ... WHERE ...
Sets an exclusive keylock on each record found in the search. The exceptions are statements that search for unique rows via a unique index, requiring only one index record to be locked. -
When
UPDATE
a clustered index record is modified, an implicit lock is taken on the affected secondary index record. When performing a duplicate check before inserting a new secondary index record, and while inserting a new secondary index record, theUPDATE
operation also sets a shared lock on the affected secondary index record. -
DELETE FROM ... WHERE ...
Sets an exclusive keylock on each record found in the search. The exceptions are statements that search for unique rows via a unique index, requiring only one index record to be locked. -
INSERT
Set an exclusive lock on the inserted row. Index record locks, rather than temporary key locks (that is, no gap locks), do not prevent other sessions from inserting new rows into preceding gaps.Before inserting a new row, an insert intention gap lock is set. Signals an insert intent, if multiple transactions want to insert a new record in the same index gap, as long as it is not the same slot, there is no need to wait.
4
Assume that the index records have values of and , respectively7
. If there are two transactions that want to insert the two values of 5 and 6 respectively, before obtaining the exclusive lock, each transaction will first set the insertion intention lock to lock the gap between 4 and 7, but there is no blocking between them, Because the lines do not conflict.If a duplicate-key error occurs, a shared lock is set on the duplicate index record. This shared lock can cause a deadlock if another session has acquired the exclusive lock and multiple sessions try to insert the same row. This happens when joining another session deletes the row. For example an InnoDB table
t1
has the following structure:CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
Suppose there are three sessions performing the following operations in sequence:
Session 1:
START TRANSACTION; INSERT INTO t1 VALUES(1);
Session 2:
START TRANSACTION; INSERT INTO t1 VALUES(1);
Session 3:
START TRANSACTION; INSERT INTO t1 VALUES(1);
Session 1:
ROLLBACK;
The first operation of session 1 acquires an exclusive lock on the row. Both session 2 and session 3 operations generate duplicate key errors and request a shared lock on the row. When session 1 rolls back, the exclusive lock on the row is released, and the requests queued for the shared lock by sessions 2 and 3 are granted. At this time, session 2 and session 3 will have a deadlock: because the other party holds a shared lock, neither session can obtain the exclusive lock of the row.
1
A similar situation occurs if the table contains rows with key values and the three sessions are executed in the following order:Session 1:
START TRANSACTION; DELETE FROM t1 WHERE i = 1;
Session 2:
START TRANSACTION; INSERT INTO t1 VALUES(1);
Session 3:
START TRANSACTION; INSERT INTO t1 VALUES(1);
Session 1:
COMMIT;
The first operation of session 1 acquires an exclusive lock on the row. Both session 2 and session 3 operations cause a duplicate key error, and then request a shared lock for the row. After session 1 commits, it releases the exclusive lock on the row and grants session 2 and session 3 shared locks on queued requests. At this time, session 2 and session 3 will have a deadlock: because the other party holds a shared lock, neither session can obtain the exclusive lock of the row.
-
INSERT ... ON DUPLICATE KEY UPDATE
Unlike simpleINSERT
statements, when a duplicate key error occurs, an exclusive rather than a shared lock is set on the row being updated. Exclusive index record locks are used for duplicate primary key values. Set an exclusive temporary key lock on duplicate unique key values. -
If there are no conflicts on the unique key, then
REPLACE
is handled theINSERT
same as . If there is a conflict, an exclusive keylock is set on the line to be replaced. -
INSERT INTO T SELECT ... FROM S WHERE ...
T
Set an exclusive record lock (without gap locks) on each row inserted . If the transaction isolation level isREAD COMMITTED
, or the transaction isolation level is notSERIALIZABLE
but enabledinnodb_locks_unsafe_for_binlog
, InnoDB searches the S table for consistent reads (no locks). In other cases, InnoDB willS
set a shared temporary key lock on the row. Why must the lock be set? The reason is that when using the statement-based bin-log for rollforward recovery, each must be executed in exactly the same way as the original operation. SQL statements.CREATE TABLE ... SELECT ...
Use a shared adjacent key lock, or do it with a consistent readSELECT
, similarlyINSERT ... SELECT
.When constructed
SELECT
withREPLACE INTO t SELECT ... FROM s WHERE ...
orUPDATE t ... WHERE col IN (SELECT ... FROM s ...)
, InnoDBs
sets shared temporary key locks on the rows of the table. -
When initializing data on a table with a specified
AUTO_INCREMENT
attribute column, InnoDB places an exclusive lock on the end of the associated index.In
innodb_autoinc_lock_mode=0
the case of , InnoDB uses a special table locking modeAUTO-INC
in which access to auto-increment counters requires the lock to be acquired and held until the end of the current SQL statement (not the entire transaction). While a table lock is heldAUTO-INC
, other clients cannot insert into the table.
And forinnodb_autoinc_lock_mode=1
bulk inserts of , the same behavior occurs.
Table-level locksAUTO-INC
andinnodb_autoinc_lock_mode=2
cannot be used together. See Section 14.6.1.6, “AUTO_INCREMENT Handling in InnoDB” for more information .InnoDB does not set any locks when retrieving previously initialized
AUTO_INCREMENT
column values. -
If constraints are defined
FOREIGN KEY
, then all inserts, updates, and deletes need to check the constraints and set shared record locks. InnoDB also sets the lock in the event of a constraint check failure. -
LOCK TABLES
Set a table lock, but set a higher-level MySQL lock than InnoDB. If it is the default valueinnodb_table_locks = 1
, andautocommit = 0
, InnoDB can perceive table locks, MySQL level will also perceive row-level locks.Otherwise, InnoDB's automatic deadlock detection cannot detect deadlocks involving such table locks. Similarly, in this case, the upper-layer MySQL does not perceive row-level locks, and may set table locks on tables that hold row-level locks for other sessions. But this does not affect transactional integrity, as described in 14.7.5.2 Deadlock Detection .
-
If default
innodb_table_locks=1
,LOCK TABLES
two locks are acquired on each table. In addition to MySQL-level table locks, an InnoDB table lock is also acquired. Versions prior to MySQL 4.1.2 do not acquire InnoDB table locks; the behavior of older versions can beinnodb_table_locks=0
simulated by specifying . If the InnoDB table lock is not obtained, if the records in the table are locked by other transactions,LOCK TABLES
the execution will be successful.In MySQL 5.7, does not work
innodb_table_locks=0
forLOCK TABLES ... WRITE
tables explicitly locked. But table locks in read mode, and read/write locks triggered implicitly byLOCK TABLES ... READ
, (such as triggers) work.LOCK TABLES ... WRITE
-
When a transaction commits or aborts, all InnoDB locks it holds are released. So in
autocommit=1
the mode,LOCK TABLES
it doesn't make sense to execute on the InnoDB table, because the acquired InnoDB table lock will be released immediately. -
Other tables cannot be locked during transaction execution because an
LOCK TABLES
implicitCOMMIT' 和
UNLOCK TABLES` is performed.
14.7.4 Phantom rows
In the same transaction, if the same query statement is executed at different time points, if different result sets are obtained, this phenomenon is called phantom reading (phantom problem).
Example: The same SELECT
statement is executed twice, but the second query returns one more row than the first query, then this row is a "phantom row (Phantom Row)".
Assuming that the column child
of the table id
has an index, query all 100
rows with an id value greater than and lock them for later update:
SELECT * FROM child WHERE id > 100 FOR UPDATE;
100
This query scans the index starting from the first record with an id value greater than . If there are two rows of data in the table whose id values 90
are sum 102
, within the scan range, other sessions may insert a new row with an id value in the table if there is no lock ( 90
to ) the gap. If the same statement is executed again in the same transaction , a new row with id will be seen in the result returned by the query , which is the phantom row. If such a row is considered a data item, then this new phantom data will violate the transaction isolation principle: the data that has been read cannot be modified during the execution of the transaction.102
101
SELECT
101
In order to prevent phantom reading, InnoDB uses an algorithm called "next-key locking", which combines index-row locking and gap locking. The way InnoDB row-level locks are executed is that when searching or scanning an index, a shared lock or an exclusive lock is set on the index records encountered. Therefore, row-level locks are essentially index-record locks.
In addition, a temporary key lock on an index record also affects the "gap" preceding that index record. That is, the immediate key lock, is the index record lock, plus the gap lock before the index record. If a session R
sets a temporary key lock (shared lock or exclusive lock) on an index record, other sessions cannot R
insert new index records in the immediately preceding gap, according to the sort order of the index.
When InnoDB scans an index, it may also lock the gap after the last record in the index. In the previous example, we demonstrated this situation: In order to prevent the insertion id
of records greater than 100 in the table, the lock set by InnoDB includes id
the gap lock after 102.
We can also use temporary key locks to implement uniqueness checks: when reading data in shared mode, if no duplicates are seen in the row to be inserted, the row can be safely inserted because the temporary key lock set when reading , which prevents other sessions from inserting duplicates later. In fact, temporary key locks can "lock" content that does not exist in the table.
For how to disable gap locks, please refer to 14.7.1 Locks in InnoDB . But it may cause phantom reading problems, because after disabling the gap lock, other sessions may insert new rows in the gap.
14.7.5 Deadlocks in InnoDB
Deadlock refers to the situation that multiple transactions cannot continue to execute because they hold the locks that each other needs. Because they are all waiting for resources, no one will release the lock they hold.
For example, through statements such as UPDATE
or SELECT ... FOR UPDATE
, when locking multiple tables or multiple rows, if they are executed in reverse order, deadlock may occur.
If the SQL statement needs to lock the index range or lock the gap, due to timing problems, when each transaction only acquires a part of the lock, deadlock will also occur.
For an example of a deadlock, refer to the following subsection 14.7.5.1 InnoDB deadlock example .
To reduce the possibility of deadlocks:
- Please use transactions, try not to use
LOCK TABLES
statements. - Make the transaction that executes insert or update small enough so that the transaction opening time will not be too long;
- When different transactions update multiple tables or a large range of rows, keep each transaction in the same order of operations;
- Create indexes on the columns used in
SELECT ... FOR UPDATE
the and statements.UPDATE ... WHERE
The possibility of deadlock is not affected by the isolation level, because the isolation level only changes the behavior of the read operation, while the deadlock occurs due to the write operation.
For how to avoid deadlocks and how to recover from deadlock conditions, please refer to 14.7.5.3 Reducing deadlocks and deadlock handling methods .
When deadlock detection is enabled (default enabled), InnoDB will automatically detect where a deadlock occurs and automatically roll back one of the transactions (called the victim, victim). If innodb_deadlock_detect
automatic deadlock detection is disabled with the option, InnoDB can only innodb_lock_wait_timeout
rollback transactions by the specified timeout period. Even if the application logic is completely correct, situations such as transaction retries need to be handled. We can use SHOW ENGINE INNODB STATUS
the command to view the most recent deadlocked transactions. If deadlocks occur frequently, transaction structure adjustments are required, or error handling is required, you can mysqld
specify the option for the startup parameter innodb_print_all_deadlocks
to print all deadlock-related information to the error log.
For information on how to perform automatic deadlock detection and handling, refer to 14.7.5.2 Deadlock Detection .
14.7.5.1 InnoDB deadlock example
The following example demonstrates what happens when a deadlock occurs. There are two clients involved in this example: A and B.
First, client A creates a table, inserts a piece of data, and then starts a transaction. In the transaction, client A obtains S
the lock of the row by querying in shared mode:
# 客户端A
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i |
+------+
| 1 |
+------+
Next, client B starts a transaction and tries to delete this row from the table:
# 客户端B
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
A delete operation requires the acquisition of X
a lock. However, because the lock is incompatible with the lock X
held by client A , the authorization cannot be obtained immediately, and the lock request that needs to be added to the row is queued for queuing, so client B is blocked.S
Client A then also tries to delete the row from the table:
# 客户端A
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
It can be seen that a deadlock has occurred here, because client A needs to acquire X
the lock before deleting the row. But since client B requested X
the lock and is waiting for client A to release S
the lock, client A's X
lock request cannot be granted.
Moreover, it is the lock requested by client B first X
, so the lock held by A S
cannot be upgraded to X
a lock. The result is that InnoDB causes one of the clients to generate an error and release the lock it holds. The error message returned by the client is similar to this:
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
Then, another client's lock request is granted and executed, deleting the row from the table.
14.7.5.2 Deadlock Detection
InnoDB will enable deadlock detection ( deadlock detection
) by default, which can automatically detect the "deadlock" generated by the transaction, and automatically roll back one or more transactions to break the deadlock state.
InnoDB will try to choose to roll back smaller transactions. As for the size of the transaction, it depends on the number of rows that have been inserted, updated, and deleted.
By default, innodb_table_locks = 1
if autocommit = 0
InnoDB perceives table locks, the upper layer MySQL can also perceive row-level locks. Otherwise, InnoDB cannot automatically detect deadlocks if table locks set by MySQL statements or locks set by other storage engines are
involved . This kind of situation can only be resolved by the timeout period set by LOCK TABLES
the system variable .innodb_lock_wait_timeout
If the InnoDB Monitor output LATEST DETECTED DEADLOCK
section contains this information: "TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION," it means that the number of transactions in the waiting list has reached 200 limits. A wait list of more than 200 transactions will be considered a deadlock, and transactions attempting to check the wait list will be rolled back. The same error may also occur if the transactions in the wait list hold more than 1 million locks, and there are lock threads to check.
For techniques on how to perform database operations to avoid deadlocks, please refer to the previous 14.7.5 Deadlocks in InnoDB .
Disable deadlock detection
innodb_deadlock_detect
Deadlock detection can be disabled using the option.
In a high-concurrency system, when multiple threads wait for the same lock, deadlock detection can lead to slow response. Sometimes innodb_lock_wait_timeout
it may be more efficient to rely on specified timeouts for transaction rollbacks than automatic deadlock detection.
14.7.5.3 Reducing Deadlock Probability, Handling Deadlock Errors
Based on the concepts introduced in the previous section 14.7.5.2 Deadlock Detection . Here's how to organize our database operations to minimize deadlocks, and how to follow up on deadlock errors in the application.
Deadlock is a typical problem in transactional relational databases, but deadlock is not terrible unless it occurs frequently and some transactions cannot be executed.
Usually, when a transaction is rolled back due to a deadlock error, our application needs to re-execute the transaction [some businesses can be manually triggered].
InnoDB uses automatic row-level locking. Even in a transaction that inserts or deletes a single row of data, a deadlock can occur. Because these operations are not really "atomic" operations; when inserting or deleting rows corresponding to (one or more) index records, the database will be automatically locked.
The technical means described below can be used to deal with deadlocks and reduce the possibility of deadlocks:
-
Use
SHOW ENGINE INNODB STATUS
the command at any time to see the cause of a recent deadlock. Can help us tune the application to avoid deadlocks. -
If deadlock warnings occur frequently, enable
innodb_print_all_deadlocks
the configure option to collect more DEBUG information. Output information about each deadlock in the MySQL "error log". After debugging, remember to disable this option. -
Be sure to re-execute the transaction if it fails due to a deadlock. Deadlock is not terrible, generally just try again.
-
Keep transactions small and allow transactions to last for a shorter period of time to reduce the chance of conflicts.
-
After making database changes, commit transactions in a timely manner to reduce the possibility of conflicts. In particular, do not leave long interactive
mysql
sessions open without committing transactions. -
If using locking reads (
SELECT ... FOR UPDATE
orSELECT ... LOCK IN SHARE MODE
), try switching to a lower isolation level, egREAD COMMITTED
. -
If you modify multiple tables, or multiple sets of data, in one transaction, perform these operations in a consistent order each time. This way transactions can form well-defined queues and will not deadlock. For example, encapsulate database operations into specific functions or service methods, or call save services instead of writing scattered INSERT, UPDATE, and DELETE statements in multiple places.
-
Add indexes appropriately. In this way, our SQL query only needs to scan a few index records, and there are fewer locked records. You can use
EXPLAIN SELECT
to determine which index the MySQL server will use by default to execute SQL queries. -
Lock less. If the data can be read from the old version of the snapshot, there is no need to use the
FOR UPDATE
orLOCK IN SHARE MODE
clause. If deadlocks often occur,READ COMMITTED
it is better to use the isolation level, because every consistent read in the same transaction is read from its own new snapshot. -
If nothing else, use table-level locks to serialize our transactions. In a storage engine that supports transactions such as InnoDB,
LOCK TABLES
the correct method to use is: first passSET autocommit = 0
(instead ofSTART TRANSACTION
) open the transaction, then execute in the transactionLOCK TABLES
, and then call it after the transaction is explicitly committedUNLOCK TABLES
. For example, if you need tot2
read data from a table and write it to a tablet1
, you can execute it in the following order:SET autocommit=0; LOCK TABLES t1 WRITE, t2 READ; ... do something with tables t1 and t2 here ... COMMIT; UNLOCK TABLES;
Table-level locks can prevent other sessions from concurrently updating this table, which avoids deadlocks, but at the cost of slower response times for high-load systems.
-
Another way to serialize transactions is to create an auxiliary "semaphore" table that contains only one row of data. Each transaction updates this row of data before reading or writing to other tables. This also ensures that all transactions are executed serially. Note that in this case, InnoDB's deadlock detection algorithm will also take effect, because this serialization operation corresponds to row-level locks. When using MySQL table lock, the deadlock problem can only be solved by timeout.
Related Links
If you see the friends here, please help me to support with Star: https://github.com/cncounter/translation/