14.7 InnoDB Locking and Transaction Model

To implement a large-scale, busy, or highly reliable database application, to port substantial code from a different database system, or to tune MySQL performance, it is important to understand 'InnoDB' locking and the 'InnoDB' transaction model.

This section discusses several topics related to 'InnoDB' locking and the 'InnoDB' transaction model with which you should be familiar.

 File: manual.info.tmp, Node: innodb-locking, Next: innodb-transaction-model, Prev: innodb-locking-transaction-model, Up: innodb-locking-transaction-model

14.7.1 InnoDB Locking

This section describes lock types used by 'InnoDB'.

Shared and Exclusive Locks

'InnoDB' implements standard row-level locking where there are two types of locks, shared ('S') locks and exclusive ('X') locks.

If transaction 'T1' holds a shared ('S') lock on row 'r', then requests from some distinct transaction 'T2' for a lock on row 'r' are handled as follows:

If a transaction 'T1' holds an exclusive ('X') lock on row 'r', a request from some distinct transaction 'T2' for a lock of either type on 'r' cannot be granted immediately. Instead, transaction 'T2' has to wait for transaction 'T1' to release its lock on row 'r'.

Intention Locks

'InnoDB' supports multiple granularity locking which permits coexistence of row locks and table locks. For example, a statement such as *note 'LOCK TABLES ... WRITE': lock-tables. takes an exclusive lock (an 'X' lock) on the specified table. To make locking at multiple granularity levels practical, 'InnoDB' uses intention locks. Intention locks are table-level locks that indicate which type of lock (shared or exclusive) a transaction requires later for a row in a table. There are two types of intention locks:

For example, note 'SELECT ... LOCK IN SHARE MODE': select. sets an 'IS' lock, and note 'SELECT ... FOR UPDATE': select. sets an 'IX' lock.

The intention locking protocol is as follows:

Table-level lock type compatibility is summarized in the following matrix.

           'X'            'IX'           'S'            'IS'
                                                        

'X'

Conflict Conflict Conflict Conflict

'IX'

Conflict Compatible Conflict Compatible

'S'

Conflict Conflict Compatible Compatible

'IS'

Conflict Compatible Compatible Compatible

A lock is granted to a requesting transaction if it is compatible with existing locks, but not if it conflicts with existing locks. A transaction waits until the conflicting existing lock is released. If a lock request conflicts with an existing lock and cannot be granted because it would cause deadlock, an error occurs.

Intention locks do not block anything except full table requests (for example, *note 'LOCK TABLES ... WRITE': lock-tables.). The main purpose of intention locks is to show that someone is locking a row, or going to lock a row in the table.

Transaction data for an intention lock appears similar to the following in note 'SHOW ENGINE INNODB STATUS': show-engine. and note InnoDB monitor: innodb-standard-monitor. output:

 TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

Record Locks

A record lock is a lock on an index record. For example, 'SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;' prevents any other transaction from inserting, updating, or deleting rows where the value of 't.c1' is '10'.

Record locks always lock index records, even if a table is defined with no indexes. For such cases, 'InnoDB' creates a hidden clustered index and uses this index for record locking. See *note innodb-index-types::.

Transaction data for a record lock appears similar to the following in note 'SHOW ENGINE INNODB STATUS': show-engine. and note InnoDB monitor: innodb-standard-monitor. output:

 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 Locks

A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record. For example, 'SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;' prevents other transactions from inserting a value of '15' into column 't.c1', whether or not there was already any such value in the column, because the gaps between all existing values in the range are locked.

A gap might span a single index value, multiple index values, or even be empty.

Gap locks are part of the tradeoff between performance and concurrency, and are used in some transaction isolation levels and not others.

Gap locking is not needed for statements that lock rows using a unique index to search for a unique row. (This does not include the case that the search condition includes only some columns of a multiple-column unique index; in that case, gap locking does occur.) For example, if the 'id' column has a unique index, the following statement uses only an index-record lock for the row having 'id' value 100 and it does not matter whether other sessions insert rows in the preceding gap:

 SELECT * FROM child WHERE id = 100;

If 'id' is not indexed or has a nonunique index, the statement does lock the preceding gap.

It is also worth noting here that conflicting locks can be held on a gap by different transactions. For example, transaction A can hold a shared gap lock (gap S-lock) on a gap while transaction B holds 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 an index, the gap locks held on the record by different transactions must be merged.

Gap locks in 'InnoDB' are 'purely inhibitive', which means that their only purpose is to prevent other transactions from inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to 'READ COMMITTED' or enable the 'innodb_locks_unsafe_for_binlog' system variable (which is now deprecated). In this case, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.

There are also other effects of using the 'READ COMMITTED' isolation level or enabling 'innodb_locks_unsafe_for_binlog'. Record locks for nonmatching rows are released after MySQL has evaluated the 'WHERE' condition. For 'UPDATE' statements, 'InnoDB' does a 'semi-consistent' read, such that it returns the latest committed version to MySQL so that MySQL can determine whether the row matches the 'WHERE' condition of the *note 'UPDATE': update.

Next-Key Locks

A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.

'InnoDB' performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. A next-key lock on an index record also affects the 'gap' before that index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record 'R' in an index, another session cannot insert a new index record in the gap immediately before 'R' in the index order.

Suppose that an index contains the values 10, 11, 13, and 20. The possible next-key locks for this index cover the following intervals, where a round bracket denotes exclusion of the interval endpoint and a square bracket denotes inclusion of the endpoint:

 (negative infinity, 10]
 (10, 11]
 (11, 13]
 (13, 20]
 (20, positive infinity)

For the last interval, the next-key lock locks the gap above the largest value in the index and the 'supremum' pseudo-record having a value higher than any value actually in the index. The supremum is not a real index record, so, in effect, this next-key lock locks only the gap following the largest index value.

By default, 'InnoDB' operates in 'REPEATABLE READ' transaction isolation level. In this case, 'InnoDB' uses next-key locks for searches and index scans, which prevents phantom rows (see *note innodb-next-key-locking::).

Transaction data for a next-key lock appears similar to the following in note 'SHOW ENGINE INNODB STATUS': show-engine. and note InnoDB monitor: innodb-standard-monitor. output:

 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 Intention Locks

An insert intention lock is a type of gap lock set by *note 'INSERT': insert. operations prior to row insertion. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.

The following example demonstrates a transaction taking an insert intention lock prior to obtaining an exclusive lock on the inserted record. The example involves two clients, A and B.

Client A creates a table containing two index records (90 and 102) and then starts a transaction that places an exclusive lock on index records with an ID greater than 100. The exclusive lock includes a gap lock before record 102:

 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 |
 +-----+

Client B begins a transaction to insert a record into the gap. The transaction takes an insert intention lock while it waits to obtain an exclusive lock.

 mysql> START TRANSACTION;
 mysql> INSERT INTO child (id) VALUES (101);

Transaction data for an insert intention lock appears similar to the following in note 'SHOW ENGINE INNODB STATUS': show-engine. and note InnoDB monitor: innodb-standard-monitor. output:

 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  ;;...

AUTO-INC Locks

An 'AUTO-INC' lock is a special table-level lock taken by transactions inserting into tables with 'AUTO_INCREMENT' columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.

The 'innodb_autoinc_lock_mode' variable controls the algorithm used for auto-increment locking. It allows you to choose how to trade off between predictable sequences of auto-increment values and maximum concurrency for insert operations.

For more information, see *note innodb-auto-increment-handling::.

Predicate Locks for Spatial Indexes

'InnoDB' supports 'SPATIAL' indexing of columns containing spatial data (see *note optimizing-spatial-analysis::).

To handle locking for operations involving 'SPATIAL' indexes, next-key locking does not work well to support 'REPEATABLE READ' or 'SERIALIZABLE' transaction isolation levels. There is no absolute ordering concept in multidimensional data, so it is not clear which is the 'next' key.

To enable support of isolation levels for tables with 'SPATIAL' indexes, 'InnoDB' uses predicate locks. A 'SPATIAL' index contains minimum bounding rectangle (MBR) values, so 'InnoDB' enforces consistent read on the index by setting a predicate lock on the MBR value used for a query. Other transactions cannot insert or modify a row that would match the query condition.

 File: manual.info.tmp, Node: innodb-transaction-model, Next: innodb-locks-set, Prev: innodb-locking, Up: innodb-locking-transaction-model

14.7.2 InnoDB Transaction Model

The 'InnoDB' transaction model aims combine the best properties of a multi-versioning database with traditional two-phase locking. 'InnoDB' performs locking at the row level and runs queries as nonlocking consistent reads by default, in the style of Oracle. The lock information in 'InnoDB' is stored space-efficiently so that lock escalation is not needed. Typically, several users are permitted to lock every row in 'InnoDB' tables, or any random subset of the rows, without causing 'InnoDB' memory exhaustion.

 File: manual.info.tmp, Node: innodb-transaction-isolation-levels, Next: innodb-autocommit-commit-rollback, Prev: innodb-transaction-model, Up: innodb-transaction-model

14.7.2.1 Transaction Isolation Levels .....................................

Transaction isolation is one of the foundations of database processing. Isolation is the I in the acronym ACID; the isolation level is the setting that fine-tunes the balance between performance and reliability, consistency, and reproducibility of results when multiple transactions are making changes and performing queries at the same time.

'InnoDB' offers all four transaction isolation levels described by the SQL:1992 standard: 'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ', and 'SERIALIZABLE'. The default isolation level for 'InnoDB' is 'REPEATABLE READ'.

A user can change the isolation level for a single session or for all subsequent connections with the note 'SET TRANSACTION': set-transaction. statement. To set the server's default isolation level for all connections, use the '--transaction-isolation' option on the command line or in an option file. For detailed information about isolation levels and level-setting syntax, see note set-transaction::.

'InnoDB' supports each of the transaction isolation levels described here using different locking strategies. You can enforce a high degree of consistency with the default 'REPEATABLE READ' level, for operations on crucial data where ACID compliance is important. Or you can relax the consistency rules with 'READ COMMITTED' or even 'READ UNCOMMITTED', in situations such as bulk reporting where precise consistency and repeatable results are less important than minimizing the amount of overhead for locking. 'SERIALIZABLE' enforces even stricter rules than 'REPEATABLE READ', and is used mainly in specialized situations, such as with XA transactions and for troubleshooting issues with concurrency and deadlocks.

The following list describes how MySQL supports the different transaction levels. The list goes from the most commonly used level to the least used.

 File: manual.info.tmp, Node: innodb-autocommit-commit-rollback, Next: innodb-consistent-read, Prev: innodb-transaction-isolation-levels, Up: innodb-transaction-model

14.7.2.2 autocommit, Commit, and Rollback .........................................

In 'InnoDB', all user activity occurs inside a transaction. If 'autocommit' mode is enabled, each SQL statement forms a single transaction on its own. By default, MySQL starts the session for each new connection with 'autocommit' enabled, so MySQL does a commit after each SQL statement if that statement did not return an error. If a statement returns an error, the commit or rollback behavior depends on the error. See *note innodb-error-handling::.

A session that has 'autocommit' enabled can perform a multiple-statement transaction by starting it with an explicit note 'START TRANSACTION': commit. or note 'BEGIN': commit. statement and ending it with a note 'COMMIT': commit. or note 'ROLLBACK': commit. statement. See *note commit::.

If 'autocommit' mode is disabled within a session with 'SET autocommit = 0', the session always has a transaction open. A note 'COMMIT': commit. or note 'ROLLBACK': commit. statement ends the current transaction and a new one starts.

If a session that has 'autocommit' disabled ends without explicitly committing the final transaction, MySQL rolls back that transaction.

Some statements implicitly end a transaction, as if you had done a note 'COMMIT': commit. before executing the statement. For details, see note implicit-commit::.

A note 'COMMIT': commit. means that the changes made in the current transaction are made permanent and become visible to other sessions. A note 'ROLLBACK': commit. statement, on the other hand, cancels all modifications made by the current transaction. Both note 'COMMIT': commit. and note 'ROLLBACK': commit. release all 'InnoDB' locks that were set during the current transaction.

Grouping DML Operations with Transactions

By default, connection to the MySQL server begins with autocommit mode enabled, which automatically commits every SQL statement as you execute it. This mode of operation might be unfamiliar if you have experience with other database systems, where it is standard practice to issue a sequence of DML statements and commit them or roll them back all together.

To use multiple-statement transactions, switch autocommit off with the SQL statement 'SET autocommit = 0' and end each transaction with note 'COMMIT': commit. or note 'ROLLBACK': commit. as appropriate. To leave autocommit on, begin each transaction with note 'START TRANSACTION': commit. and end it with note 'COMMIT': commit. or *note 'ROLLBACK': commit. The following example shows two transactions. The first is committed; the second is rolled back.

 $> mysql test

 mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
 Query OK, 0 rows affected (0.00 sec)
 mysql> -- Do a transaction with autocommit turned on.
 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> -- Do another transaction with autocommit turned off.
 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> -- Now we undo those last 2 inserts and the delete.
 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-Side Languages

In APIs such as PHP, Perl DBI, JDBC, ODBC, or the standard C call interface of MySQL, you can send transaction control statements such as note 'COMMIT': commit. to the MySQL server as strings just like any other SQL statements such as note 'SELECT': select. or *note 'INSERT': insert. Some APIs also offer separate special transaction commit and rollback functions or methods.

 File: manual.info.tmp, Node: innodb-consistent-read, Next: innodb-locking-reads, Prev: innodb-autocommit-commit-rollback, Up: innodb-transaction-model

14.7.2.3 Consistent Nonlocking Reads ....................................

A consistent read means that 'InnoDB' uses multi-versioning to present to a query a snapshot of the database at a point in time. The query sees the changes made by transactions that committed before that point in time, and no changes made by later or uncommitted transactions. The exception to this rule is that the query sees the changes made by earlier statements within the same transaction. This exception causes the following anomaly: If you update some rows in a table, a *note 'SELECT': select. sees the latest version of the updated rows, but it might also see older versions of any rows. If other sessions simultaneously update the same table, the anomaly means that you might see the table in a state that never existed in the database.

If the transaction isolation level is 'REPEATABLE READ' (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction. You can get a fresher snapshot for your queries by committing the current transaction and after that issuing new queries.

With 'READ COMMITTED' isolation level, each consistent read within a transaction sets and reads its own fresh snapshot.

Consistent read is the default mode in which 'InnoDB' processes *note 'SELECT': select. statements in 'READ COMMITTED' and 'REPEATABLE READ' isolation levels. A consistent read does not set any locks on the tables it accesses, and therefore other sessions are free to modify those tables at the same time a consistent read is being performed on the table.

Suppose that you are running in the default 'REPEATABLE READ' isolation level. When you issue a consistent read (that is, an ordinary *note 'SELECT': select. statement), 'InnoDB' gives your transaction a timepoint according to which your query sees the database. If another transaction deletes a row and commits after your timepoint was assigned, you do not see the row as having been deleted. Inserts and updates are treated similarly.

Note:

The snapshot of the database state applies to note 'SELECT': select. statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a note 'DELETE': delete. or *note 'UPDATE': update. statement issued from another concurrent 'REPEATABLE READ' transaction could affect those just-committed rows, even though the session could not query them. If a transaction does update or delete rows committed by a different transaction, those changes do become visible to the current transaction. For example, you might encounter a situation like the following:

 SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
 -- Returns 0: no rows match.
 DELETE FROM t1 WHERE c1 = 'xyz';
 -- Deletes several rows recently committed by other transaction.

 SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
 -- Returns 0: no rows match.
 UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
 -- Affects 10 rows: another txn just committed 10 rows with 'abc' values.
 SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
 -- Returns 10: this txn can now see the rows it just updated.

You can advance your timepoint by committing your transaction and then doing another note 'SELECT': select. or note 'START TRANSACTION WITH CONSISTENT SNAPSHOT': commit.

This is called multi-versioned concurrency control.

In the following example, session A sees the row inserted by B only when B has committed the insert and A has committed as well, so that the timepoint is advanced past the commit of B.

              Session A              Session B

            SET autocommit=0;      SET autocommit=0;
 time
 |          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 see the 'freshest' state of the database, use either the 'READ COMMITTED' isolation level or a locking read:

 SELECT * FROM t LOCK IN SHARE MODE;

With 'READ COMMITTED' isolation level, each consistent read within a transaction sets and reads its own fresh snapshot. With 'LOCK IN SHARE MODE', a locking read occurs instead: A 'SELECT' blocks until the transaction containing the freshest rows ends (see *note innodb-locking-reads::).

Consistent read does not work over certain DDL statements:

The type of read varies for selects in clauses like note 'INSERT INTO ... SELECT': insert, note 'UPDATE ... (SELECT)': update, and *note 'CREATE TABLE ... SELECT': create-table. that do not specify 'FOR UPDATE' or 'LOCK IN SHARE MODE':

 File: manual.info.tmp, Node: innodb-locking-reads, Prev: innodb-consistent-read, Up: innodb-transaction-model

14.7.2.4 Locking Reads ......................

If you query data and then insert or update related data within the same transaction, the regular 'SELECT' statement does not give enough protection. Other transactions can update or delete the same rows you just queried. 'InnoDB' supports two types of locking reads that offer extra safety:

These clauses are primarily useful when dealing with tree-structured or graph-structured data, either in a single table or split across multiple tables. You traverse edges or tree branches from one place to another, while reserving the right to come back and change any of these 'pointer' values.

All locks set by 'LOCK IN SHARE MODE' and 'FOR UPDATE' queries are released when the transaction is committed or rolled back.

Note:

Locking reads are only possible when autocommit is disabled (either by beginning transaction with *note 'START TRANSACTION': commit. or by setting 'autocommit' to 0.

A locking read clause in an outer statement does not lock the rows of a table in a nested subquery unless a locking read clause is also specified in the subquery. For example, the following statement does not lock rows in table 't2'.

 SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;

To lock rows in table 't2', add a locking read clause to the subquery:

 SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;

Locking Read Examples

Suppose that you want to insert a new row into a table 'child', and make sure that the child row has a parent row in table 'parent'. Your application code can ensure referential integrity throughout this sequence of operations.

First, use a consistent read to query the table 'PARENT' and verify that the parent row exists. Can you safely insert the child row to table 'CHILD'? No, because some other session could delete the parent row in the moment between your 'SELECT' and your 'INSERT', without you being aware of it.

To avoid this potential issue, perform the *note 'SELECT': select. using 'LOCK IN SHARE MODE':

 SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

After the 'LOCK IN SHARE MODE' query returns the parent ''Jones'', you can safely add the child record to the 'CHILD' table and commit the transaction. Any transaction that tries to acquire an exclusive lock in the applicable row in the 'PARENT' table waits until you are finished, that is, until the data in all tables is in a consistent state.

For another example, consider an integer counter field in a table 'CHILD_CODES', used to assign a unique identifier to each child added to table 'CHILD'. Do not use either consistent read or a shared mode read to read the present value of the counter, because two users of the database could see the same value for the counter, and a duplicate-key error occurs if two transactions attempt to add rows with the same identifier to the 'CHILD' table.

Here, 'LOCK IN SHARE MODE' is not a good solution because if two users read the counter at the same time, at least one of them ends up in deadlock when it attempts to update the counter.

To implement reading and incrementing the counter, first perform a locking read of the counter using 'FOR UPDATE', and then increment the counter. For example:

 SELECT counter_field FROM child_codes FOR UPDATE;
 UPDATE child_codes SET counter_field = counter_field + 1;

A note 'SELECT ... FOR UPDATE': select. reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL note 'UPDATE': update. would set on the rows.

The preceding description is merely an example of how *note 'SELECT ... FOR UPDATE': select. works. In MySQL, the specific task of generating a unique identifier actually can be accomplished using only a single access to the table:

 UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
 SELECT LAST_INSERT_ID();

The *note 'SELECT': select. statement merely retrieves the identifier information (specific to the current connection). It does not access any table.

 File: manual.info.tmp, Node: innodb-locks-set, Next: innodb-next-key-locking, Prev: innodb-transaction-model, Up: innodb-locking-transaction-model

14.7.3 Locks Set by Different SQL Statements in InnoDB

A locking read, an note 'UPDATE': update, or a note 'DELETE': delete. generally set record locks on every index record that is scanned in the processing of an SQL statement. It does not matter whether there are 'WHERE' conditions in the statement that would exclude the row. 'InnoDB' does not remember the exact 'WHERE' condition, but only knows which index ranges were scanned. The locks are normally next-key locks that also block inserts into the 'gap' immediately before the record. However, gap locking can be disabled explicitly, which causes next-key locking not to be used. For more information, see note innodb-locking::. The transaction isolation level can also affect which locks are set; see note innodb-transaction-isolation-levels::.

If a secondary index is used in a search and the index record locks to be set are exclusive, 'InnoDB' also retrieves the corresponding clustered index records and sets locks on them.

If you have no indexes suitable for your statement and MySQL must scan the entire table to process the statement, every row of the table becomes locked, which in turn blocks all inserts by other users to the table. It is important to create good indexes so that your queries do not scan more rows than necessary.

'InnoDB' sets specific types of locks as follows.

 File: manual.info.tmp, Node: innodb-next-key-locking, Next: innodb-deadlocks, Prev: innodb-locks-set, Up: innodb-locking-transaction-model

14.7.4 Phantom Rows

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a *note 'SELECT': select. is executed twice, but returns a row the second time that was not returned the first time, the row is a 'phantom' row.

Suppose that there is an index on the 'id' column of the 'child' table and that you want to read and lock all rows from the table having an identifier value larger than 100, with the intention of updating some column in the selected rows later:

 SELECT * FROM child WHERE id > 100 FOR UPDATE;

The query scans the index starting from the first record where 'id' is bigger than 100. Let the table contain rows having 'id' values of 90 and 102. If the locks set on the index records in the scanned range do not lock out inserts made in the gaps (in this case, the gap between 90 and 102), another session can insert a new row into the table with an 'id' of 101. If you were to execute the same *note 'SELECT': select. within the same transaction, you would see a new row with an 'id' of 101 (a 'phantom') in the result set returned by the query. If we regard a set of rows as a data item, the new phantom child would violate the isolation principle of transactions that a transaction should be able to run so that the data it has read does not change during the transaction.

To prevent phantoms, 'InnoDB' uses an algorithm called next-key locking that combines index-row locking with gap locking. 'InnoDB' performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. In addition, a next-key lock on an index record also affects the 'gap' before the index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record 'R' in an index, another session cannot insert a new index record in the gap immediately before 'R' in the index order.

When 'InnoDB' scans an index, it can also lock the gap after the last record in the index. Just that happens in the preceding example: To prevent any insert into the table where 'id' would be bigger than 100, the locks set by 'InnoDB' include a lock on the gap following 'id' value 102.

You can use next-key locking to implement a uniqueness check in your application: If you read your data in share mode and do not see a duplicate for a row you are going to insert, then you can safely insert your row and know that the next-key lock set on the successor of your row during the read prevents anyone meanwhile inserting a duplicate for your row. Thus, the next-key locking enables you to 'lock' the nonexistence of something in your table.

Gap locking can be disabled as discussed in *note innodb-locking::. This may cause phantom problems because other sessions can insert new rows into the gaps when gap locking is disabled.

 File: manual.info.tmp, Node: innodb-deadlocks, Prev: innodb-next-key-locking, Up: innodb-locking-transaction-model

14.7.5 Deadlocks in InnoDB

A deadlock is a situation in which multiple transactions are unable to proceed because each transaction holds a lock that is needed by another one. Because all transactions involved are waiting for the same resource to become available, none of them ever releases the lock it holds.

A deadlock can occur when transactions lock rows in multiple tables (through statements such as note 'UPDATE': update. or note 'SELECT ... FOR UPDATE': select.), but in the opposite order. A deadlock can also occur when such statements lock ranges of index records and gaps, with each transaction acquiring some locks but not others due to a timing issue. For a deadlock example, see *note innodb-deadlock-example::.

To reduce the possibility of deadlocks, use transactions rather than note 'LOCK TABLES': lock-tables. statements; keep transactions that insert or update data small enough that they do not stay open for long periods of time; when different transactions update multiple tables or large ranges of rows, use the same order of operations (such as note 'SELECT ... FOR UPDATE': select.) in each transaction; create indexes on the columns used in note 'SELECT ... FOR UPDATE': select. and note 'UPDATE ... WHERE': update. statements. The possibility of deadlocks is not affected by the isolation level, because the isolation level changes the behavior of read operations, while deadlocks occur because of write operations. For more information about avoiding and recovering from deadlock conditions, see *note innodb-deadlocks-handling::.

When deadlock detection is enabled (the default) and a deadlock does occur, 'InnoDB' detects the condition and rolls back one of the transactions (the victim). If deadlock detection is disabled using the 'innodb_deadlock_detect' variable, 'InnoDB' relies on the 'innodb_lock_wait_timeout' setting to roll back transactions in case of a deadlock. Thus, even if your application logic is correct, you must still handle the case where a transaction must be retried. To view the last deadlock in an 'InnoDB' user transaction, use note 'SHOW ENGINE INNODB STATUS': show-engine. If frequent deadlocks highlight a problem with transaction structure or application error handling, enable 'innodb_print_all_deadlocks' to print information about all deadlocks to the note 'mysqld': mysqld. error log. For more information about how deadlocks are automatically detected and handled, see *note innodb-deadlock-detection::.

 File: manual.info.tmp, Node: innodb-deadlock-example, Next: innodb-deadlock-detection, Prev: innodb-deadlocks, Up: innodb-deadlocks

14.7.5.1 An InnoDB Deadlock Example ...................................

The following example illustrates how an error can occur when a lock request causes a deadlock. The example involves two clients, A and B.

First, client A creates a table containing one row, and then begins a transaction. Within the transaction, A obtains an 'S' lock on the row by selecting it in share mode:

 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 begins a transaction and attempts to delete the row from the table:

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

 mysql> DELETE FROM t WHERE i = 1;

The delete operation requires an 'X' lock. The lock cannot be granted because it is incompatible with the 'S' lock that client A holds, so the request goes on the queue of lock requests for the row and client B blocks.

Finally, client A also attempts to delete the row from the table:

 mysql> DELETE FROM t WHERE i = 1;

Deadlock occurs here because client A needs an 'X' lock to delete the row. However, that lock request cannot be granted because client B already has a request for an 'X' lock and is waiting for client A to release its 'S' lock. Nor can the 'S' lock held by A be upgraded to an 'X' lock because of the prior request by B for an 'X' lock. As a result, 'InnoDB' generates an error for one of the clients and releases its locks. The client returns this error:

 ERROR 1213 (40001): Deadlock found when trying to get lock;
 try restarting transaction

At that point, the lock request for the other client can be granted and it deletes the row from the table.

 File: manual.info.tmp, Node: innodb-deadlock-detection, Next: innodb-deadlocks-handling, Prev: innodb-deadlock-example, Up: innodb-deadlocks

14.7.5.2 Deadlock Detection ...........................

When deadlock detection is enabled (the default), 'InnoDB' automatically detects transaction deadlocks and rolls back a transaction or transactions to break the deadlock. 'InnoDB' tries to pick small transactions to roll back, where the size of a transaction is determined by the number of rows inserted, updated, or deleted.

'InnoDB' is aware of table locks if 'innodb_table_locks = 1' (the default) and 'autocommit = 0', and the MySQL layer above it knows about row-level locks. Otherwise, 'InnoDB' cannot detect deadlocks where a table lock set by a MySQL *note 'LOCK TABLES': lock-tables. statement or a lock set by a storage engine other than 'InnoDB' is involved. Resolve these situations by setting the value of the 'innodb_lock_wait_timeout' system variable.

If the 'LATEST DETECTED DEADLOCK' section of 'InnoDB' Monitor output includes a message stating, ''TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION',' this indicates that the number of transactions on the wait-for list has reached a limit of 200. A wait-for list that exceeds 200 transactions is treated as a deadlock and the transaction attempting to check the wait-for list is rolled back. The same error may also occur if the locking thread must look at more than 1,000,000 locks owned by transactions on the wait-for list.

For techniques to organize database operations to avoid deadlocks, see *note innodb-deadlocks::.

Disabling Deadlock Detection

On high concurrency systems, deadlock detection can cause a slowdown when numerous threads wait for the same lock. At times, it may be more efficient to disable deadlock detection and rely on the 'innodb_lock_wait_timeout' setting for transaction rollback when a deadlock occurs. Deadlock detection can be disabled using the 'innodb_deadlock_detect' variable.

 File: manual.info.tmp, Node: innodb-deadlocks-handling, Prev: innodb-deadlock-detection, Up: innodb-deadlocks

14.7.5.3 How to Minimize and Handle Deadlocks .............................................

This section builds on the conceptual information about deadlocks in *note innodb-deadlock-detection::. It explains how to organize database operations to minimize deadlocks and the subsequent error handling required in applications.

Deadlocks are a classic problem in transactional databases, but they are not dangerous unless they are so frequent that you cannot run certain transactions at all. Normally, you must write your applications so that they are always prepared to re-issue a transaction if it gets rolled back because of a deadlock.

'InnoDB' uses automatic row-level locking. You can get deadlocks even in the case of transactions that just insert or delete a single row. That is because these operations are not really 'atomic'; they automatically set locks on the (possibly several) index records of the row inserted or deleted.

You can cope with deadlocks and reduce the likelihood of their occurrence with the following techniques:

 File: manual.info.tmp, Node: innodb-configuration, Next: innodb-compression, Prev: innodb-locking-transaction-model, Up: innodb-storage-engine