MySQL 5.6 Reference Manual-14.3 InnoDB Transaction Model and Locking
14.3 InnoDB Transaction Model and Locking
14.3.2 InnoDB Record, Gap, and Next-Key Locks
14.3.3 Avoiding the Phantom Problem Using Next-Key Locking
14.3.4 Consistent Nonlocking Reads
14.3.5 Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)
14.3.6 Locks Set by Different SQL Statements in InnoDB
14.3.7 Implicit Transaction Commit and Rollback
14.3.8 Deadlock Detection and Rollback
14.3.9 How to Cope with Deadlocks
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, you must understand the notions of transactions and locking as they relate to the InnoDB storage engine.
为了实现大规模,业务繁忙,高可靠的数据库应用,针对不同的数据库系统需要累计大量的代码来。对于MySQL的性能调优,你必须要理解InnoDB存储引擎相关的事务和锁的概念。
In the InnoDB transaction model, the goal is to combine the best properties of a multi-versioning database with traditional two-phase locking. InnoDB does locking on the row level and runs queries as nonlocking consistent reads by default, in the style of Oracle. The lock information in InnoDB is stored so space-efficiently 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.
在InnoDB事务模型中,通过传统的two-phase locking来实现多版本数据库的最佳性能组合。InnoDB默认情况下InnoDB使用的是行级锁,并且以以没有锁的一致性读的方式来运行查询,和Oracle的方式比较相似。InnoDB存储的锁信息在空间上是非常有效率的,以至于锁不需要升级:通常情况下,多个用户被允许锁住InnoDB表的每一行或者随机行数的集合而不会耗尽内存。
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 Section 14.19.4, “InnoDB Error Handling”.
在InnoDB里,所有用户的动作都在事务里。如果开启了autocommit模式,每个SQL语句形成它自己单独的事务。默认情况下,MySQL为每个开启autocommit的新连接开启事务,所以如果语句没有报错的话每个语句结束后都会自动commit。如果语句报错的话,将会基于错误来决定是commit或者rollback。详见Section 14.19.4, “InnoDB Error Handling”。
A session that has autocommit enabled can perform a multiple-statement transaction by starting it with an explicit START TRANSACTION or BEGIN statement and ending it with a COMMIT or ROLLBACK statement. See Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Syntax”.
开启autocommit的session在显式的START TRANSACTION or BEGIN语句开启,COMMIT or ROLLBACK结尾的事务中可以执行多个语句。详见Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Syntax”。
If autocommit mode is disabled within a session with SET autocommit = 0, the session always has a transaction open. A COMMIT or ROLLBACK statement ends the current transaction and a new one starts.
如果使用autocommit = 0关闭了autocommit模式,这个session永远是事务打开的,对于每个事务都要以COMMIT or ROLLBACK语句结尾。
A COMMIT means that the changes made in the current transaction are made permanent and become visible to other sessions. A ROLLBACK statement, on the other hand, cancels all modifications made by the current transaction. Both COMMIT and ROLLBACK release all InnoDB locks that were set during the current transaction.
COMMIT意味这当前事务的修改会被持久化,对于其他session也变得是可见的。在另一方面,ROLLBACK会取消当前事务的所有修改。COMMIT and ROLLBACK会释放当前事务中设置所有InnoDB锁。
In terms of the SQL:1992 transaction isolation levels, the default InnoDB level is REPEATABLE READ. InnoDB offers all four transaction isolation levels described by the SQL standard: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE.
在SQL:1992标准的事务隔离级别,InnoDB默认的级别是REPEATABLE READ。InnoDB提供了SQL标准描述的所有四种事务隔离级别:READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE。
A user can change the isolation level for a single session or for all subsequent connections with the 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 Section 13.3.6, “SET TRANSACTION Syntax”.
用户可以为单独的session或者随后所有的连接通过SET TRANSACTION语句修改隔离级别。对于针对所有连接的实例默认的隔离级别,可以使用--transaction-isolation参数或者参数文件的方式来设置。关于隔离级别的详细信息及相关设置语法,可以查看Section 13.3.6, “SET TRANSACTION Syntax”。
In row-level locking, InnoDB normally uses next-key locking. That means that besides index records, InnoDB can also lock the gap preceding an index record to block insertions by other sessions where the indexed values would be inserted in that gap within the tree data structure. A next-key lock refers to a lock that locks an index record and the gap before it. A gap lock refers to a lock that locks only the gap before some index record.
在行级锁里,InnoDB通常使用next-key locking。这意味着除了索引记录,InnoDB还能够锁住索引记录之前的间隙,以阻塞其他session在索引树的结构里往这个间隙里插入新的索引记录。next-key lock表示会锁住索引记录以及它之前的间隙。gap lock只会锁住索引记录之前的间隙。
For more information about row-level locking, and the circumstances under which gap locking is disabled, see Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”.
更多关于行级锁,以及什么情况下gap lock会被关闭,可以查看Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”。
14.3.1 InnoDB Lock Modes
InnoDB implements standard row-level locking where there are two types of locks, shared (S) locks and exclusive (X) locks. For information about record, gap, and next-key lock types, see Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”.
InnoDB实现了标准的行级锁,锁的类型有两种,shared (S) locks and exclusive (X) locks。更多关于记录,gap,next-key锁的类型,可以查看Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”。
l A shared (S) lock permits the transaction that holds the lock to read a row.
l shared (S) lock允许事务在读的时候持有锁。
l An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.
l exclusive (X) lock允许事务在update或者delete的时候持有锁。
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:
如果事务T1在行r上持有shared (S) lock,那么在行r上来自于事务T2的不同请求将会以下面的方式处理:
l A request by T2 for an S lock can be granted immediately. As a result, both T1 and T2 hold an S lock on r.
l T2的S锁的请求将会立刻被授予。结果是T1和T2都会在r上持有S锁。
l A request by T2 for an X lock cannot be granted immediately.
l T2的X锁请求不会被立刻授予。
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.
如果事务T1在行r上持有exclusive (X) lock,那么T2在r上的任何类型锁都不会立刻被授予。相反,事务T2需要等待事务T1来释放它在行r上的锁。
Intention Locks
Additionally, InnoDB supports multiple granularity locking which permits coexistence of record locks and locks on entire tables. To make locking at multiple granularity levels practical, additional types of locks called intention locks are used. Intention locks are table locks in InnoDB that indicate which type of lock (shared or exclusive) a transaction will require later for a row in that table. There are two types of intention locks used in InnoDB (assume that transaction T has requested a lock of the indicated type on table t):
此外,InnoDB支持行级别和表级别的这样不同粒度的锁。要实现不同粒度的锁,又加了一个锁的类型叫做意向锁(intention locks)。intention lock在InnoDB里是表级锁,这表明了事务之后才能决定锁的类型(shared or exclusive)。InnoDB使用了两种类型的intention lock(假设事务T在表t上有上述锁的请求):
l Intention shared (IS): Transaction T intends to set S locks on individual rows in table t.
l Intention shared (IS): 事务T打算在表t的个别行上设置S锁。
l Intention exclusive (IX): Transaction T intends to set X locks on those rows.
l Intention exclusive (IX): 事务T打算在行上设置X锁。
For example, SELECT ... LOCK IN SHARE MODE sets an IS lock and SELECT ... FOR UPDATE sets an IX lock.
例如,SELECT ... LOCK IN SHARE MODE设置的是IS锁,SELECT ... FOR UPDATE设置的是IX锁。
The intention locking protocol is as follows:
intention lock的协议如下:
l Before a transaction can acquire an S lock on a row in table t, it must first acquire an IS or stronger lock on t.
l 在事务请求一个S锁之前,首先会要求一个IS锁或者更强的锁。
l Before a transaction can acquire an X lock on a row, it must first acquire an IX lock on t.
l 在事务请求X锁之前,首先要有IX锁。
These rules can be conveniently summarized by means of the following lock type compatibility 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.
事务被授予的锁如果和现有的锁兼容(compatible ),那就不会有冲突(conflicts )。对于和当前有冲突的锁,事务会一直等待到其被释放。如果请求的锁和当前的锁有冲突,而且还不能被授予,这就会引起死锁,并会返回一个错误。
Thus, intention locks do not block anything except full table requests (for example, LOCK TABLES ... WRITE). The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.
因此,intention lock不会阻塞任何东西除非是全表的请求(例如,LOCK TABLES ... WRITE)。IX and IS锁的主要目的是显示出有一些行记录被锁,或者将要在这些行上加锁。
Deadlock Example
The following example illustrates how an error can occur when a lock request would cause a deadlock. The example involves two clients, A and B.
下面的例子说明了当一个请求的锁是如何引起死锁的并报错的。这个例子包含了两个客户端A和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:
首先,A创建了一个表只有一行记录,然后开启了一个事务。在这个事务里,A在行上获得了一个S锁:
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:
然后,B开启了事务并试图删除这行记录:
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.
delete操作要求X锁。这个锁不能立刻被授予因为它和A所持有的S锁是不兼容的,所以它会被放置在这行记录的锁请求的队列中,B的操作也会被阻塞。
Finally, client A also attempts 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
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:
这样就发生了死锁,因为A需要X锁来删除记录。然而,这个锁又不能立刻会授予因为B已经有了一个X锁的请求而且在等待A去释放它的S锁。A所持有的S锁也不能升级到X锁因为之前B以in个请求了X锁。结果,InnoDB会为其中的一个客户端产生一个错误并释放其所持有的锁。客户端得到的错误如下:
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.
在那个时刻,其他客户的的锁请求会被授予,删除操作也会被执行。
Note
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, which is defined by LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK. 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.
InnoDB Monitor输出的LATEST DETECTED DEADLOCK章节包括一个信息说明,“TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION,”事务等待列表的数量已经达到了200的限制,这个限制是由LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK定义的。超过200个事务的等待列表将会被以死锁来对待,事务也将会试图检查被回滚的等待列表。
The same error may also occur if the locking thread must look at more than 1,000,000 locks owned by the transactions on the wait-for list. The limit of 1,000,000 locks is defined by LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK.
如果锁线程必须要查看等待列表里事务拥有的超过1,000,000个的锁,也会发生同样的错误。1,000,000个锁的限制是由LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK定义的。
14.3.2 InnoDB Record, Gap, and Next-Key Locks
InnoDB has several types of record-level locks including record locks, gap locks, and next-key locks. For information about shared locks, exclusive locks, and intention locks, see Section 14.3.1, “InnoDB Lock Modes”.
InnoDB有几种行级锁包括record lock,gap lock,和next-key lock。关于shared locks, exclusive locks, and intention locks的信息查看Section 14.3.1, “InnoDB Lock Modes”。
l Record lock: This is a lock on an index record.
l Record lock: 这种锁在索引的记录上。
l Gap lock: This is a lock on a gap between index records, or a lock on the gap before the first or after the last index record.
l Gap lock: 这种锁在索引记录之间的间隙,或者是第一个索引记录之前的间隙或者是最后一个索引记录之后的间隙。
l Next-key lock: This is a combination of a record lock on the index record and a gap lock on the gap before the index record.
l Next-key lock: 这是索引记录上的record lock和索引记录之前间隙上的gap lock的组合。
Record Locks
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 Section 14.2.5.2, “Clustered and Secondary Indexes”.
record lock总是锁在索引记录上,即使是表上没有定义索引。例如这样的一个情况,InnoDB创建了一个隐藏的clustered index,并利用这个索引做record lock。详见Section 14.2.5.2, “Clustered and Secondary Indexes”。
Next-key Locks
By default, InnoDB operates in REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 14.3.3, “Avoiding the Phantom Problem Using Next-Key Locking”).
默认情况下,InnoDB工作在REPEATABLE READ事务隔离级别以及innodb_locks_unsafe_for_binlog关闭的情况下。在这种情况下,InnoDB使用next-key lock来搜索及索引扫描,这能够阻止幻影行记录(详见Section 14.3.3, “Avoiding the Phantom Problem Using Next-Key Locking”)。
Next-key locking 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 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.
next-key lock是index-row和gap lock的组合。InnoDB以行级锁的方式进行搜索或者索引扫描,它会在索引记录上加共享或者排他锁。因此,行级锁实际指的是索引记录锁。另外,索引记录上的next-key lock还会影响索引记录之前的“间隙(gap)”。那就是说,next-key lock是index-record lock加上了索引记录之前的gap lock。如果一个session在索引记录R上有一个共享或者排他锁,其他session就不能立刻插入在R之前的间隙里插入一个新的索引记录。
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:
假设一个索引包含值10, 11, 13, and 20。这个索引的可能的next-key lock会覆盖下面的间隔,小括号表示不包含,中括号表示包含:
(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.
对于最后的间隔,next-key lock会锁住索引最大值上面的间隙以及一个大于任意一个这个索引实际值的值。最小上界(supremum)是一个真实的索引记录,所以实际上next-key lock只会锁住最大索引值后面的间隙。
Gap Locks
The next-key locking example in the previous section shows that a gap might span a single index value, multiple index values, or even be empty.
上文next-key lock的例子显示了一个gap能够跨越过单个索引值,多个索引值,甚至于是空的空间。
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:
通过唯一索引来搜索唯一行的语句是不需要gap lock的。(这种情况不包括搜索条件只包括多列多列条件的某些列;在这种情况下,gap lock还是要发生的。)例如,如果id列上有唯一索引,下面的语句只hi使用一个索引记录来查找id值是100的行记录,而不会去管其他session插入的值是否在gap里。
SELECT * FROM child WHERE id = 100;
If id is not indexed or has a nonunique index, the statement does lock the preceding gap.
如果id列上没有索引或者有的只是一个非唯一索引,那么语句会锁住前面的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上。例如,事务A持有一个shared gap lock (gap S-lock)在gap上,同时事务B也持有一个exclusive gap lock (gap X-lock)在相同的gap上。之所以这样是因为如果一个记录从索引上被purge,那么gap lock冲突是被允许的,但是不同事务在记录上持有的gap lock是必须要合并的。
Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. They do not prevent different transactions from taking gap locks on the same gap. Thus, a gap X-lock has the same effect as a gap S-lock.
gap lock在InnoDB里是“纯粹禁止的(purely inhibitive)”,这就意味着只有停止其他往gap里insert的事务。它们不会阻止不同的事务在相同的gap里去持有gap lock。因此,gap X-lock和gap S-lock的影响是相同的。
A type of gap lock called an insert intention gap lock is set by 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.
一种被称之为insert intention gap lock是在行插入之前的insert操作设置的。这种锁表示意图以这样的方式进行insert:多个事务如果它们没有insert到gap中同一个位置时,那么它们insert到相同的index gap时不需要相互等待对方。假设有索引记录值4和7。分离的事务试图分别插入索引值5和6,每个优先的在4和7之间的insert intention lock都在插入的行上包含exclusive lock,但是它们不会相互阻塞,因为行数据是不冲突的。
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.
下面的例子显示了一个事务使得优先的的insert intention lock在插入的记录上包含了exclusive lock。这个例子涉及了两个客户端,A和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:
A创建了一个表包含了两个索引记录(90 and 102),然后启动一个事务放置一个exclusive lock在ID大于100的索引记录上。这个exclusive lock包括了记录102之前的gap lock。
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.
B开启了一个事务向gap里插入一条记录。这个事务在登台获得一个exclusive lock的时候会得到一个insert intention lock。
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
To view data about the insert intention lock, run SHOW ENGINE INNODB STATUS. Data similar to the following appears under the TRANSACTIONS heading:
通过运行SHOW ENGINE INNODB STATUS可以查看insert intention lock的数据。这个数据显示在TRANSACTIONS标头的下面。
mysql> SHOW ENGINE INNODB STATUS\G
...
SHOW ENGINE INNODB STATUS
---TRANSACTION 8731, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 3, OS thread handle 0x7f996beac700, query id 30 localhost root update
INSERT INTO child (id) VALUES (101)
------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
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 ;;...
Disabling Gap Locking
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). Under these circumstances, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.
gap lock也是可以显示关闭的。如果你把事务隔离级别修改为READ COMMITTED或者开启innodb_locks_unsafe_for_binlog系统变量(这个变量已经被废弃),那么对于搜索和索引扫描gap lock就会被关闭,它只会用于外键约束的检查和重复键的检查。
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 UPDATE.
对于使用READ COMMITTED事务隔离级别或者开启innodb_locks_unsafe_for_binlog还有其他的影响:在MySQL评估WHERE条件之后不匹配的record lock将会被释放。对于UPDATE语句来说,InnoDB会执行“半一致性(semi-consistent)”读,它会返回最近commit的版本给MySQL,那么MySQL就可以决定行记录是否和UPDATE语句的WHERE条件相匹配。
14.3.3 Avoiding the Phantom Problem Using Next-Key Locking
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 SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
所谓的幻读(phantom )问题发生在一个事务里相同的查询在不同的时间点上产生了不同的结果集。例如,SELECT语句执行了两次,但是第二次执行的时候返回了一行而第一执行的时候没有记录被返回,那么这行就是”phantom”行。
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:
假设child表的id列上有一个索引,你想要读取并锁住所有值大于100的记录:
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 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.
这个查询扫描索引从第一个id大于100的记录开始。这里假设表包含值是90和102两个值的记录。如果扫描范围内在索引记录上的锁没能把gap(在这个情况下,gap是 90到102)上的insert阻拦在外的话,其他session能够insert一个新的id是101的行到表里。如果你的相同的事务里还执行了相同的SELECT语句,你会看到id是101的新行(“phantom”行)会在查询的结果集里。如果我们注意到结果集,就会发现新的phantom结果违反了事务隔离的原则:事务里读到数据不会改变。
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 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.
为了避免phantoms,InnoDB使用了next-key lock,既包括了index-row lock,又包括了gap lock。InnoDB以在索引记录上加锁的方式执行行级锁。因此,这个行级锁实际上是index-record lock。另外,索引记录上的next-record lock也影响这索引记录之前的gap。那就是说,next-key lock是一个index-record lock加上索引记录之前gap上的gap lock。如果一个session在索引记录R上持有了一个shared or exclusive lock,其他session就不能立即在索引记录R之前插入一个新的索引记录。
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.
当InnoDB扫描索引的时候,它也会锁住索引最后记录之后的gap。这只会发生在之前的例子里:为了阻止插入新的id值大于100的记录,InnoDB设置的锁包括了一个在102后面的gap上锁。
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.
你能够使用next-key lock来在你的应用里实现唯一性检查:如果你在共享模式读取里数据,而没有看到重复的行,然后你就可以安全地插入这些行。这是因为在读的过程中InnoDB已经把next-key lock放置在了这些行的后续上,这也就阻止了其他任何人同时插入重复的数据。因此,next-key lock能够使得你”锁住“表里不存在的一些东西。
Gap locking can be disabled as discussed in Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”. This may cause phantom problems because other sessions can insert new rows into the gaps when gap locking is disabled.
gap lock也能够被关闭,如 Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”所描述的。这会引起phantom问题因为其他的session在gap lock关闭的情况下能够向gap里插入新的记录。
14.3.4 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 of 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 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.
一致性读意味着InnoDB使用多版本查询在某个一个时间点上数据库快照。查询只能看到事务提交时间点之前的变化,而看不到之后以及未提交事务的变化。例外的情况是在相同事务里查询可以看到早先语句产生的变化。这个例外会引起下面的异常:如果你更新了表里的一部分数据,SELECT会看到更新数据的最新版本,但是也会看到老版本的数据。如果其他session同时更新了相同的表,这个异常也就意味着你能够阿奎那到一个从未在数据库存在的表的状态。
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.
如果事务隔离级别是REPEATABLE READ(默认的级别),所有相同事务里的一致性读会读那个在事务开始第一个能够读到的已经确认的快照。你可以通过提交当前事务然后再执行新的查询才能够得到更新版本的快照。
With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot.
在READ COMMITTED隔离级别下,事务里的每个一致性读会设置读取它自己最新的快照。
Consistent read is the default mode in which InnoDB processes 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.
在READ COMMITTED and REPEATABLE READ隔离级别下一致性读是InnoDB处理SELECT语句时默认的一种模式。一致性读不会在访问的表上设置任何的锁,因此在相同的时间里执行一致性读的时候其他session也能够修改这些表。
Suppose that you are running in the default REPEATABLE READ isolation level. When you issue a consistent read (that is, an ordinary 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.
假设你运行在默认的REPEATABLE READ隔离级别下。当你执行了一个一致性读(也就是说是一个普通的SELELCT语句),InnoDB会根据查询数据库时给你的事务一个时间点。如果其他事务在这个时间点之后删除了一行并提交了,那你就不会看到已经被删除的记录。insert和update也会以相同的方式处理。
Note
The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a DELETE or 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语句,但对DML语句来说不是必须的。如果你在事务里插入,修改了一些行数据,其他并发的REPEATABLE READ事务执行delete或者update会影响这些已提交的行,即使这个session并不能查询到它们。如果一个update或者delete其他不同事务里已提交的行数据,那这些修改对当前事务会变得可见的。例如,你可能遭遇到下弥漫的情况:
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 SELECT or START TRANSACTION WITH CONSISTENT SNAPSHOT.
你可以通过提交事务来推进时间点,然后再执行其他的SELECT或者START TRANSACTION WITH CONSISTENT SNAPSHOT。
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只有在A和B都提交了以后才能看到B所插入的数据,所以时间点推进到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:
如果你想要看到最新的数据库状态,可以使用READ COMMITTED隔离级别或者这样的锁读:
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 Section 14.3.5, “Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)”).
在READ COMMITTED隔离级别下,事务里的每一个一致性读都会设置读取它最新的快照。使用LOCK IN SHARE MODE,则会发生一个锁读:SELECT阻塞直到事务包含的最新的行结束(详见Section 14.3.5, “Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)”)。
Consistent read does not work over certain DDL statements:
在某些DDL语句上一致性读是不能工作的:
l Consistent read does not work over DROP TABLE, because MySQL cannot use a table that has been dropped and InnoDB destroys the table.
l 在DROP TABLE的时候一致性读是不工作的,因为MySQL不会使用已经被删除的表,InnoDB会破坏表的。
l Consistent read does not work over ALTER TABLE, because that statement makes a temporary copy of the original table and deletes the original table when the temporary copy is built. When you reissue a consistent read within a transaction, rows in the new table are not visible because those rows did not exist when the transaction's snapshot was taken. In this case, the transaction returns an error as of MySQL 5.6.6: ER_TABLE_DEF_CHANGED, “Table definition has changed, please retry transaction”.
l 在ALTER TABLE的时候一致性读不会工作,因为ALTER TABLE会为原是表做个一个临时拷贝,但临时拷贝建立完成之后会删除原是表。当你在事务里执行一致性读,新表里面的行是不可见的因为在获得事务快照的时候那些行是不存在的。在这种情况下,MySQL会返回一个错误ER_TABLE_DEF_CHANGED, “Table definition has changed, please retry transaction”。
The type of read varies for selects in clauses like INSERT INTO ... SELECT, UPDATE ... (SELECT), and CREATE TABLE ... SELECT that do not specify FOR UPDATE or LOCK IN SHARE MODE:
这种类型的读也适用于select子句像INSERT INTO ... SELECT, UPDATE ... (SELECT),以及没i有指定FOR UPDATE or LOCK IN SHARE MODE的CREATE TABLE ... SELECT:
l By default, InnoDB uses stronger locks and the SELECT part acts like READ COMMITTED, where each consistent read, even within the same transaction, sets and reads its own fresh snapshot.
l 默认情况下,InnoDB使用更强劲的锁,SELECT部分的动作像READ COMMITTED,每个一致性读,即使是相同事务里的,也会设置并读取它最新的快照。
l To use a consistent read in such cases, enable the innodb_locks_unsafe_for_binlog option and set the isolation level of the transaction to READ UNCOMMITTED, READ COMMITTED, or REPEATABLE READ (that is, anything other than SERIALIZABLE). In this case, no locks are set on rows read from the selected table.
l 在使用一致性读的情况有:开启innodb_locks_unsafe_for_binlog并设置事务的隔离级别为READ UNCOMMITTED,或者是用READ COMMITTED和REPEATABLE READ(也就是说,只要不是SERIALIZABLE)。在这种情况下,读取的行上不会设置锁。
14.3.5 Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)
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:
如果你先查询数据然后再在相同的事务里insert或者update相关的数据,常规的SELECT语句并不能够提供足够的保护。其他的事务依然能够update或者delete你刚才查询的的相同的数据。InnoDB提供两种类型的读锁来提供额外的安全保障:
l SELECT ... LOCK IN SHARE MODE sets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.
l SELECT ... LOCK IN SHARE MODE会在读取的行上加一个共享模式的锁。其他的session能够读这些数据,但是在你当前事务提交之前它们不能修改。如果这些数据的任何部分被其他事务修改了而且还未提交,你的查询会一种等待直到那个事务结束了,然后你能够看到的也是最新的版本的数据值。
l For index records the search encounters, SELECT ... FOR UPDATE locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows. Other transactions are blocked from updating those rows, from doing SELECT ... LOCK IN SHARE MODE, or from reading the data in certain transaction isolation levels. Consistent reads ignore any locks set on the records that exist in the read view. (Old versions of a record cannot be locked; they are reconstructed by applying undo logs on an in-memory copy of the record.)
l 对于遭遇到的每个索引记录,SELECT ... FOR UPDATE会锁着行记录以及任何相关的索引条目,就如同你在这些行数据上执行了UPDATE语句。其他的事务会被修改行数据,执行SELECT ... LOCK IN SHARE MODE,或者在某个隔离级别的事务里读数据的这些操作所阻塞。但一致性读会忽略这些记录上的任何一个锁。(旧版本的记录不会被阻塞;它们会通过内存里记录拷贝中的适用的undo log进行重建。)
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.
这在处理树状结构或者图结构数据的时候会非常有用,不论是单表还是多表处理上。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.
当事务commit或者rollback的时候LOCK IN SHARE MODE and FOR UPDATE查询中所有放置的锁都会被释放。
Note
Locking of rows for update using SELECT FOR UPDATE only applies when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0. If autocommit is enabled, the rows matching the specification are not locked.
只有autocommit被关闭的时候使用SELECT FOR UPDATE才会适用于对要update的行加锁(或者通过START TRANSACTION或者设置autocommit为0都可以)。如果autocommit是开启的,指定条件的行记录不会被上锁。
Usage 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.
假设你想要向表里插入一条新的child记录,要确保child行在已经在parent表里存在了parent行。你的应用代码需要确保参考这个操作顺序的完整性。
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.
首先,要查询PARENT表来确认parent是否存在。那这样就能够安全地向CHILD表插入child行了吗?不行,因为一些其他的session可能会在你SELECT和INSERT操作的这段时间里删除了parent行,而你去完全没有意识的这点。
To avoid this potential issue, perform the SELECT using LOCK IN SHARE MODE:
要避免这种潜在的问题,可以使用LOCK IN SHARE MODE执行下面的SELECT:
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 read or write to the applicable row in the PARENT table waits until you are finished, that is, the data in all tables is in a consistent state.
在LOCK IN SHARE MODE查询返回了parent 'Jones'之后,你可以安全地添加child记录到CHILD表然后commit事务。任何事务想要试图读或者写PARENT表里相关的行都要等待直到你的事务结束,也就是说所有表里的数据都是一致状态的。
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.
另一个例子,考虑在CHILD_CODES表里用一个整型的计数列,用于为CHILD表里每个新加的child行分配一个唯一的标识符。既不要使用一致性读也不要使用共享模式读来这个计数器的当前值,因为这样才能狼数据库的两个用户看到相同的计数值。那样如果两个事务试图向CHILD表里加入相同标识的行时就会产生duplicate-key的错误。
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.
这里,LOCK IN SHARE MODE不是一个好的解决方案,因为如果两个用户在相同时间读这个计数器的值,但一个试图修改这个计数器时,它们中间至少有一个会在死锁里自己结束。
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:
为了实现读和计数器的增值,首先使用FOR UPDATE执行一个读锁,然后再增加计数器的值。例如:
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.
SELECT ... FOR UPDATE会读出最近可用的值,并在它读取的每一行上放置一个排他锁。因此,it sets the same locks a searched SQL UPDATE would set on the rows.
The preceding description is merely an example of how SELECT ... FOR UPDATE works. In MySQL, the specific task of generating a unique identifier actually can be accomplished using only a single access to the table:
前面描述的仅仅是一个SELECT ... FOR UPDATE是如何工作的例子。在MySQL里,产生唯一标识的特殊工作实际上可以通过一个单独的表访问来完成的:
UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();
The SELECT statement merely retrieves the identifier information (specific to the current connection). It does not access any table.
SELECT语句检索的仅是标识信息(特指当前的连接)。它不会访问任何表。
14.3.6 Locks Set by Different SQL Statements in InnoDB
A locking read, an UPDATE, or a DELETE generally set record locks on every index record that is scanned in the processing of the 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 Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”. The transaction isolation level also can affect which locks are set; see Section 13.3.6, “SET TRANSACTION Syntax”.
UPDATE或者DELETE会在SQL语句处理扫描的每个索引记录上行锁。而且有无WHERE条件都是没关系的。InnoDB不会牢记准确的WHERE条件,但是会知道哪些索引范围被扫描了。这里锁通常是next-key lock,可以用来立即阻塞记录之前的“gap”。然而,gap lock也是可以显示关闭的,这就会使得next-key lock不能再使用。更多的信息可以查看Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”。事务隔离记录影响的锁可以查看Section 13.3.6, “SET TRANSACTION Syntax”。
If a secondary index is used in a search and index record locks to be set are exclusive, InnoDB also retrieves the corresponding clustered index records and sets locks on them.
如果用secondary index来搜索,而且索引记录上放置的是排他锁,InnoDB还会检索其相关的clustered index记录并在上面加锁。
Differences between shared and exclusive locks are described in Section 14.3.1, “InnoDB Lock Modes”.
共享和排他锁的区别可以查看Section 14.3.1, “InnoDB Lock Modes”。
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 unnecessarily scan many rows.
如果你的语句使用合适的索引可用,那么MySQL在处理语句的时候就必须要扫描整个表,那么表里的每行记录都会被锁,还会阻塞其他用户的向表里插入记录。创建一个好的索引让你的查询语句不必要扫描过多的行记录是非常重要的。
For SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE, locks are acquired for scanned rows, and expected to be released for rows that do not qualify for inclusion in the result set (for example, if they do not meet the criteria given in the WHERE clause). However, in some cases, rows might not be unlocked immediately because the relationship between a result row and its original source is lost during query execution. For example, in a UNION, scanned (and locked) rows from a table might be inserted into a temporary table before evaluation whether they qualify for the result set. In this circumstance, the relationship of the rows in the temporary table to the rows in the original table is lost and the latter rows are not unlocked until the end of query execution.
对于SELECT ... FOR UPDATE或者 SELECT ... LOCK IN SHARE MODE,扫描的行上也会加锁,expected to be released for rows that do not qualify for inclusion in the result set(例如,不在WHERE条件里)。然而,在一些情况下,行记录不会立刻被解锁因为the relationship between a result row and its original source is lost during query execution.例如,在UNION里,表里扫描的(被锁住的)行肯呢个会在评估它们是否符合结果集之前就被插入到了临时表里。在这种环境下,临时表里的行和原始表里行的关系会丢失,后来的行就不会被解锁直到查询执行完成。
InnoDB sets specific types of locks as follows.
InnoDB会设置如下几种类型的锁。
l SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters.
l SELECT ... FROM做的是一致性读,除非事务隔离级别是SERIALIZABLE,通常情况会读数据库的快照,不会放置锁。对于SERIALIZABLE级别,搜索会在其遭遇的索引记录上放置shared next-key locks。
l SELECT ... FROM ... LOCK IN SHARE MODE sets shared next-key locks on all index records the search encounters.
l SELECT ... FROM ... LOCK IN SHARE MODE会在搜索遭遇的所有索引记录上放置shared next-key locks。
l For index records the search encounters, SELECT ... FROM ... FOR UPDATE blocks other sessions from doing SELECT ... FROM ... LOCK IN SHARE MODE or from reading in certain transaction isolation levels. Consistent reads will ignore any locks set on the records that exist in the read view.
l 对于搜索遭遇的索引记录,SELECT ... FROM ... FOR UPDATE会阻塞其他session的SELECT ... FROM ... LOCK IN SHARE MODE操作,或者是某个事务隔离级别的读操作。一致性读会忽略记录上所有的锁。
l UPDATE ... WHERE ... sets an exclusive next-key lock on every record the search encounters.
l UPDATE ... WHERE ... 会在搜索遭遇到的每个记录上放置一个exclusive next-key lock 。
l DELETE FROM ... WHERE ... sets an exclusive next-key lock on every record the search encounters.
l DELETE FROM ... WHERE ...会在搜索遭遇的每个记录上放置一个an exclusive next-key lock。
l INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
l INSERT会在插入的行上放置一个exclusive lock。这个锁是一个索引记录锁,不是next-key lock(也就是说不存在gap lock),它不会阻拦其他session像插入的记录之前的gap里再插入数据。
Prior to inserting the row, a type of gap lock called an insertion intention gap lock is set. 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 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.
在插入记录之前,会放置一个称之为insertion intention gap lock的gap lock。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. 假设有索引记录值4和7。假设不同的事务试图使用insert intention lock(也包含在插入的行的排他锁)向4和7之间的gap插入5和6,但是它们不会相互阻塞因为这些行是不冲突的。
If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row. Suppose that an InnoDB table t1 has the following structure:
如果发生了重复键的错误,可以在重复索引记录上放置一个共享锁。但是这样做也会产生死锁。因为如果其他session已经持有了排他锁对歌session再试图插入相同的就会造成死锁。这会发生在其他session删除行的时候。假设有一个InnoDB表,结果如下:
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
Now suppose that three sessions perform the following operations in order:
现在假设有三个session依次执行下面的操作:
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 by session 1 acquires an exclusive lock for the row. The operations by sessions 2 and 3 both result in a duplicate-key error and they both request a shared lock for the row. When session 1 rolls back, it releases its exclusive lock on the row and the queued shared lock requests for sessions 2 and 3 are granted. At this point, sessions 2 and 3 deadlock: Neither can acquire an exclusive lock for the row because of the shared lock held by the other.
第一个操作session 1在行上获得一个排他锁。session 2和3都会获得重复键的错误,它们都会在行上获得一个共享锁。当session 1会滚了,它释放了在行上的排他锁,session 2和3请求的共享锁队列会被授予。在这个时间上,session 2和3相互死锁:谁都不能获得改行上的排他锁因为对方都持有了共享锁。
A similar situation occurs if the table already contains a row with key value 1 and three sessions perform the following operations in order:
一个相似情况也会发生:如果表已经包含了一个值1,三个session按顺序执行下面的操作:
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 by session 1 acquires an exclusive lock for the row. The operations by sessions 2 and 3 both result in a duplicate-key error and they both request a shared lock for the row. When session 1 commits, it releases its exclusive lock on the row and the queued shared lock requests for sessions 2 and 3 are granted. At this point, sessions 2 and 3 deadlock: Neither can acquire an exclusive lock for the row because of the shared lock held by the other.
第一个操作session 1在行上获得一个排他锁。session 2和3都会获得重复键的错误,它们都会在行上获得一个共享锁。当session 1提交了,它会释放它在行上的排他锁,session 2和3请求的共享锁队列将被授予。在这个时间点上,session 2和3相互死锁:谁都不能获得改行上的排他锁因为对方都持有了共享锁。
l INSERT ... ON DUPLICATE KEY UPDATE differs from a simple INSERT in that an exclusive next-key lock rather than a shared lock is placed on the row to be updated when a duplicate-key error occurs.
l INSERT ... ON DUPLICATE KEY UPDATE不同于简单的INSERT,当发生duplicate-key错误的时候它在要更新的行上放置的是exclusive next-key lock而不是共享锁。
l REPLACE is done like an INSERT if there is no collision on a unique key. Otherwise, an exclusive next-key lock is placed on the row to be replaced.
l REPLACE会和INSERT一样处理如果唯一键冲突的话。否则,会在更要替换的行上放置exclusive next-key lock。
l INSERT INTO T SELECT ... FROM S WHERE ... sets an exclusive index record lock (without a gap lock) on each row inserted into T. If the transaction isolation level is READ COMMITTED, or innodb_locks_unsafe_for_binlog is enabled and the transaction isolation level is not SERIALIZABLE, InnoDB does the search on S as a consistent read (no locks). Otherwise, InnoDB sets shared next-key locks on rows from S. InnoDB has to set locks in the latter case: In roll-forward recovery from a backup, every SQL statement must be executed in exactly the same way it was done originally.
l INSERT INTO T SELECT ... FROM S WHERE ...会在每个插入到T的行上放置exclusive index record lock(没有gap lock)。如果事务隔离级别是READ COMMITTED,或者innodb_locks_unsafe_for_binlog开启事务隔离级别不是SERIALIZABLE,InnoDB会在S上做一致性读(没有锁)的搜索操作。否则,InnoDB会在来自于S的行上放置shared next-key locks。对于后者InnoDB将会放置锁:在从备份的roll-forward recovery,每个SQL语句都会明确从其原始的方式执行。
CREATE TABLE ... SELECT ... performs the SELECT with shared next-key locks or as a consistent read, as for INSERT ... SELECT.
CREATE TABLE ... SELECT ...在执行SELECT的时候会使用shared next-key locks,或者会使用一致性读,如同INSERT ... SELECT。
When a SELECT is used in the constructs REPLACE INTO t SELECT ... FROM s WHERE ... or UPDATE t ... WHERE col IN (SELECT ... FROM s ...), InnoDB sets shared next-key locks on rows from table s.
当SELECT被用于构造REPLACE INTO t SELECT ... FROM s WHERE ...或者UPDATE t ... WHERE col IN (SELECT ... FROM s ...)的时候,InnoDB会在来自于表s的行上放置shared next-key lock。
l While initializing a previously specified AUTO_INCREMENT column on a table, InnoDB sets an exclusive lock on the end of the index associated with the AUTO_INCREMENT column. In accessing the auto-increment counter, InnoDB uses a specific AUTO-INC table lock mode where the lock lasts only to the end of the current SQL statement, not to the end of the entire transaction. Other sessions cannot insert into the table while the AUTO-INC table lock is held; see Section 14.3, “InnoDB Transaction Model and Locking”.
l 当初始化先前指定的AUTO_INCREMENT列的时候,InnoDB会在AUTO_INCREMENT列的相关索引的结尾放置一个排他锁。在访问自增计数器的时候,InnoDB使用指定AUTO-INC table lock模式,这种锁只会持续到当前SQL语句的结束,而不是整个事务的结束。当持有AUTO-INC table lock的时候其他session是不能够向表里插入数据的;详见Section 14.3, “InnoDB Transaction Model and Locking”。
InnoDB fetches the value of a previously initialized AUTO_INCREMENT column without setting any locks.
InnoDB获取先前初始化AUTO_INCREMENT列的值而不需要设置任何的锁。
l If a FOREIGN KEY constraint is defined on a table, any insert, update, or delete that requires the constraint condition to be checked sets shared record-level locks on the records that it looks at to check the constraint. InnoDB also sets these locks in the case where the constraint fails.
l 如果在表上定义了外键约束,任何insert,update,或者delete请求的约束条件都要设置shared record-level lock来做检查。即使约束失败InnoDB还是会设置这些锁的。
l LOCK TABLES sets table locks, but it is the higher MySQL layer above the InnoDB layer that sets these locks. InnoDB is aware of table locks if innodb_table_locks = 1 (the default) and autocommit = 0, and the MySQL layer above InnoDB knows about row-level locks.
l LOCK TABLES会放置表锁,但是这些锁是放在高于InnoDB层的MySQL层上的。如果innodb_table_locks = 1 (the default)以及autocommit = 0,InnoDB放置的是表锁,InnoDB上面额MySQL层会认为是行级锁。
Otherwise, InnoDB's automatic deadlock detection cannot detect deadlocks where such table locks are involved. Also, because in this case the higher MySQL layer does not know about row-level locks, it is possible to get a table lock on a table where another session currently has row-level locks. However, this does not endanger transaction integrity, as discussed in Section 14.3.8, “Deadlock Detection and Rollback”. See also Section 14.6.7, “Limits on InnoDB Tables”.
否则,InnoDB's automatic deadlock detection cannot detect deadlocks where such table locks are involved。因为这种情况下更高的MySQL层不知道行级锁,当其他session使用了行级锁那有可能会使用表锁。然而,这不会危害到事务的完整性,如果Section 14.3.8, “Deadlock Detection and Rollback”所描述的。或者查看Section 14.6.7, “Limits on InnoDB Tables”。
14.3.7 Implicit Transaction Commit and Rollback
By default, MySQL starts the session for each new connection with autocommit mode 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 Section 14.19.4, “InnoDB Error Handling”.
默认情况下,MySQL为每个新连接开始的session使用的是autocommit模式,如果没有返回错误的话MySQL会在每个SQL语句后面进行提交。如果语句报错,会根据错误来决定执行commit或者rollback。详见Section 14.19.4, “InnoDB Error Handling”。
If a session that has autocommit disabled ends without explicitly committing the final transaction, MySQL rolls back that transaction.
如果一个session是autocommit关闭的,而且在事务的最后也没有显式地commit,那么MySQL会回滚这个事务。
Some statements implicitly end a transaction, as if you had done a COMMIT before executing the statement. For details, see Section 13.3.3, “Statements That Cause an Implicit Commit”.
一些语句会隐式地结束事务,就如在执行这些语句之前执行了commit一样。详见Section 13.3.3, “Statements That Cause an Implicit Commit”。
14.3.8 Deadlock Detection and Rollback
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会自动发现事务死锁并回滚一个事务或者从死锁中终止事务。InnoDB会试图选哪小的事务来进行回滚,事务的大小决定了insert,update或者delete的行的数量。
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 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.
如果innodb_table_locks = 1 (the default) and autocommit = 0的时候InnoDB is aware of table locks,但它上面的MySQL层会认为是行级锁。否则,MySQL LOCK TABLES语句设置的表锁或者InnoDB之外其他存储引擎设置的锁中,InnoDB会探测不到死锁。通过设置innodb_lock_wait_timeout系统变量可以解决这个问题。
When InnoDB performs a complete rollback of a transaction, all locks set by the transaction are released. However, if just a single SQL statement is rolled back as a result of an error, some of the locks set by the statement may be preserved. This happens because InnoDB stores row locks in a format such that it cannot know afterward which lock was set by which statement.
当InnoDB执行了一个事务的完整的回滚,这个事务锁放置的所有锁都会被释放。然而,如果单个SQL语句因为结果错误而进行的回滚,这个语句放置的一些锁将会继续被放置着。之所以发生这种情况是因为InnoDB存储的行锁的方式不会知道这个语句以后放置的锁。
If a SELECT calls a stored function in a transaction, and a statement within the function fails, that statement rolls back. Furthermore, if ROLLBACK is executed after that, the entire transaction rolls back.
如果事务里的SELECT使用了存储函数,而且函数调用失败了,这个语句会回滚。此外,如果在那子厚执行了rollback,那整个事务都会回滚。
For techniques to organize database operations to avoid deadlocks, see Section 14.3.9, “How to Cope with Deadlocks”.
对于如何组织数据库操作来避免死锁,可以查看Section 14.3.9, “How to Cope with Deadlocks”。
14.3.9 How to Cope with Deadlocks
This section builds on the conceptual information about deadlocks in Section 14.3.8, “Deadlock Detection and Rollback”. It explains how to organize database operations to minimize deadlocks and the subsequent error handling required in applications.
这章构建在Section 14.3.8, “Deadlock Detection and Rollback”所讲述的死锁的概念信息上。这里讲述了如何组织数据操作最小化死锁的可能性,以及在应用中如何处理随后的错误。
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.
InnoDB使用自动的行级锁。你可以在insert或者delete的事务里得到死锁事件。这是因为这些操作不是真正的“原子性的(atomic)”;它们会自动锁住插入或者修改的行的索引记录。
You can cope with deadlocks and reduce the likelihood of their occurrence with the following techniques:
你可以使用下面的技术来处理死锁以及降低其发生的可能性:
l At any time, issue the SHOW ENGINE INNODB STATUS command to determine the cause of the most recent deadlock. That can help you to tune your application to avoid deadlocks.
l 在任何时候,执行SHOW ENGINE INNODB STATUS命令来查看最近死锁发生的原因。这能够帮助你调整应用程序来避免死锁。
l If frequent deadlock warnings cause concern, collect more extensive debugging information by enabling the innodb_print_all_deadlocks configuration option. Information about each deadlock, not just the latest one, is recorded in the MySQL error log. Disable this option when you are finished debugging.
l 如果频繁发生死锁警告,可以开启innodb_print_all_deadlocks配置参数来收集更广泛的debug信息。这样每个死锁的信息,而不仅是最近一个的,都会被记录在MySQL的错误日志里。在完成debug后记得关闭这个参数。
l Always be prepared to re-issue a transaction if it fails due to deadlock. Deadlocks are not dangerous. Just try again.
l 如果因为死锁而发生的错误,要总是准备好再执行一次。死锁没有危险,尽管重试。
l Keep transactions small and short in duration to make them less prone to collision.
l 短小的事务发生碰撞的可能行更小。
l Commit transactions immediately after making a set of related changes to make them less prone to collision. In particular, do not leave an interactive mysql session open for a long time with an uncommitted transaction.
l 立刻提交事务能够减少碰撞额可能性。特别是,不在在未提交的事务里长时间使用交互式的session。
l If you use locking reads (SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE), try using a lower isolation level such as READ COMMITTED.
l 如果你使用读锁(SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE),可以尝试使用更低隔离级别例如READ COMMITTED。
l When modifying multiple tables within a transaction, or different sets of rows in the same table, do those operations in a consistent order each time. Then transactions form well-defined queues and do not deadlock. For example, organize database operations into functions within your application, or call stored routines, rather than coding multiple similar sequences of INSERT, UPDATE, and DELETE statements in different places.
l 如果在事务里修改多个表,或者同个表里的不同的数据集,那就每次以一致的顺序去执行这些操作。这些定义明确顺序的事务会产生死锁。例如,在应用里把原始的数据库操作放在一个函数里,或者使用存储程序,而不是在代码里以不同的顺序执行INSERT,UPDATE,和DELETE语句。
l Add well-chosen indexes to your tables. Then your queries need to scan fewer index records and consequently set fewer locks. Use EXPLAIN SELECT to determine which indexes the MySQL server regards as the most appropriate for your queries.
l 添加选择性的好的索引。这样你的查询就只需要扫描更少的索引记录,从而设置更少的锁。使用EXPLAIN SELECT来确认查询使用了哪个索引。
l Use less locking. If you can afford to permit a SELECT to return data from an old snapshot, do not add the clause FOR UPDATE or LOCK IN SHARE MODE to it. Using the READ COMMITTED isolation level is good here, because each consistent read within the same transaction reads from its own fresh snapshot.
l 使用更少的锁。如果你能够承受旧版本的数据,那就不要使用FOR UPDATE或者LOCK IN SHARE MODE子句了。在这里使用READ COMMITTED各级级别是不错的选择,因为相同事务里每个一致性读都只会读它自身最新的快照。
l If nothing else helps, serialize your transactions with table-level locks. The correct way to use LOCK TABLES with transactional tables, such as InnoDB tables, is to begin a transaction with SET autocommit = 0 (not START TRANSACTION) followed by LOCK TABLES, and to not call UNLOCK TABLES until you commit the transaction explicitly. For example, if you need to write to table t1 and read from table t2, you can do this:
l 如果没有其他的帮助,使用表级别的锁串行化你的事务。正确的方式是在事务表(如InnoDB表)上使用LOCK TABLES,使用SET autocommit = 0(不是START TRANSACTION)开启一个事务随后再跟着LOCK TABLES,不要调用UNLOCK TABLES直到你显式地commit事务。例如,你需要向t1表里写数据,从t2表里读数据,可以这么做:
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;
Table-level locks prevent concurrent updates to the table, avoiding deadlocks at the expense of less responsiveness for a busy system.
表级别的锁会阻止其他对表的并发更新,以更少响应能力的代价避免死锁。
l Another way to serialize transactions is to create an auxiliary “semaphore” table that contains just a single row. Have each transaction update that row before accessing other tables. In that way, all transactions happen in a serial fashion. Note that the InnoDB instant deadlock detection algorithm also works in this case, because the serializing lock is a row-level lock. With MySQL table-level locks, the timeout method must be used to resolve deadlocks.
l 另一个串行化事务的方式是创建一个辅助的“semaphore”表只包含一行记录。每个事务在访问其他表之前都要更新这行记录。这种方式下,所有的事务都是串行的。要注意的是InnoDB探测死锁的算法也是以这种方式工作的,因为串行锁是一个行级锁。如果使用MySQL的表级锁,那还要使用timeout的方式来解决死锁。
======================================================================下面是重复内容======================================================================
14.3 InnoDB Transaction Model and Locking
14.3.2 InnoDB Record, Gap, and Next-Key Locks
14.3.3 Avoiding the Phantom Problem Using Next-Key Locking
14.3.4 Consistent Nonlocking Reads
14.3.5 Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)
14.3.6 Locks Set by Different SQL Statements in InnoDB
14.3.7 Implicit Transaction Commit and Rollback
14.3.8 Deadlock Detection and Rollback
14.3.9 How to Cope with Deadlocks
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, you must understand the notions of transactions and locking as they relate to the InnoDB storage engine.
为了实现大规模,业务繁忙,高可靠的数据库应用,针对不同的数据库系统需要累计大量的代码来。对于MySQL的性能调优,你必须要理解InnoDB存储引擎相关的事务和锁的概念。
In the InnoDB transaction model, the goal is to combine the best properties of a multi-versioning database with traditional two-phase locking. InnoDB does locking on the row level and runs queries as nonlocking consistent reads by default, in the style of Oracle. The lock information in InnoDB is stored so space-efficiently 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.
在InnoDB事务模型中,通过传统的two-phase locking来实现多版本数据库的最佳性能组合。InnoDB默认情况下InnoDB使用的是行级锁,并且以以没有锁的一致性读的方式来运行查询,和Oracle的方式比较相似。InnoDB存储的锁信息在空间上是非常有效率的,以至于锁不需要升级:通常情况下,多个用户被允许锁住InnoDB表的每一行或者随机行数的集合而不会耗尽内存。
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 Section 14.19.4, “InnoDB Error Handling”.
在InnoDB里,所有用户的动作都在事务里。如果开启了autocommit模式,每个SQL语句形成它自己单独的事务。默认情况下,MySQL为每个开启autocommit的新连接开启事务,所以如果语句没有报错的话每个语句结束后都会自动commit。如果语句报错的话,将会基于错误来决定是commit或者rollback。详见Section 14.19.4, “InnoDB Error Handling”。
A session that has autocommit enabled can perform a multiple-statement transaction by starting it with an explicit START TRANSACTION or BEGIN statement and ending it with a COMMIT or ROLLBACK statement. See Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Syntax”.
开启autocommit的session在显式的START TRANSACTION or BEGIN语句开启,COMMIT or ROLLBACK结尾的事务中可以执行多个语句。详见Section 13.3.1, “START TRANSACTION, COMMIT, and ROLLBACK Syntax”。
If autocommit mode is disabled within a session with SET autocommit = 0, the session always has a transaction open. A COMMIT or ROLLBACK statement ends the current transaction and a new one starts.
如果使用autocommit = 0关闭了autocommit模式,这个session永远是事务打开的,对于每个事务都要以COMMIT or ROLLBACK语句结尾。
A COMMIT means that the changes made in the current transaction are made permanent and become visible to other sessions. A ROLLBACK statement, on the other hand, cancels all modifications made by the current transaction. Both COMMIT and ROLLBACK release all InnoDB locks that were set during the current transaction.
COMMIT意味这当前事务的修改会被持久化,对于其他session也变得是可见的。在另一方面,ROLLBACK会取消当前事务的所有修改。COMMIT and ROLLBACK会释放当前事务中设置所有InnoDB锁。
In terms of the SQL:1992 transaction isolation levels, the default InnoDB level is REPEATABLE READ. InnoDB offers all four transaction isolation levels described by the SQL standard: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE.
在SQL:1992标准的事务隔离级别,InnoDB默认的级别是REPEATABLE READ。InnoDB提供了SQL标准描述的所有四种事务隔离级别:READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE。
A user can change the isolation level for a single session or for all subsequent connections with the 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 Section 13.3.6, “SET TRANSACTION Syntax”.
用户可以为单独的session或者随后所有的连接通过SET TRANSACTION语句修改隔离级别。对于针对所有连接的实例默认的隔离级别,可以使用--transaction-isolation参数或者参数文件的方式来设置。关于隔离级别的详细信息及相关设置语法,可以查看Section 13.3.6, “SET TRANSACTION Syntax”。
In row-level locking, InnoDB normally uses next-key locking. That means that besides index records, InnoDB can also lock the gap preceding an index record to block insertions by other sessions where the indexed values would be inserted in that gap within the tree data structure. A next-key lock refers to a lock that locks an index record and the gap before it. A gap lock refers to a lock that locks only the gap before some index record.
在行级锁里,InnoDB通常使用next-key locking。这意味着除了索引记录,InnoDB还能够锁住索引记录之前的间隙,以阻塞其他session在索引树的结构里往这个间隙里插入新的索引记录。next-key lock表示会锁住索引记录以及它之前的间隙。gap lock只会锁住索引记录之前的间隙。
For more information about row-level locking, and the circumstances under which gap locking is disabled, see Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”.
更多关于行级锁,以及什么情况下gap lock会被关闭,可以查看Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”。
14.3.1 InnoDB Lock Modes
InnoDB implements standard row-level locking where there are two types of locks, shared (S) locks and exclusive (X) locks. For information about record, gap, and next-key lock types, see Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”.
InnoDB实现了标准的行级锁,锁的类型有两种,shared (S) locks and exclusive (X) locks。更多关于记录,gap,next-key锁的类型,可以查看Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”。
l A shared (S) lock permits the transaction that holds the lock to read a row.
l shared (S) lock允许事务在读的时候持有锁。
l An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.
l exclusive (X) lock允许事务在update或者delete的时候持有锁。
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:
如果事务T1在行r上持有shared (S) lock,那么在行r上来自于事务T2的不同请求将会以下面的方式处理:
l A request by T2 for an S lock can be granted immediately. As a result, both T1 and T2 hold an S lock on r.
l T2的S锁的请求将会立刻被授予。结果是T1和T2都会在r上持有S锁。
l A request by T2 for an X lock cannot be granted immediately.
l T2的X锁请求不会被立刻授予。
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.
如果事务T1在行r上持有exclusive (X) lock,那么T2在r上的任何类型锁都不会立刻被授予。相反,事务T2需要等待事务T1来释放它在行r上的锁。
Intention Locks
Additionally, InnoDB supports multiple granularity locking which permits coexistence of record locks and locks on entire tables. To make locking at multiple granularity levels practical, additional types of locks called intention locks are used. Intention locks are table locks in InnoDB that indicate which type of lock (shared or exclusive) a transaction will require later for a row in that table. There are two types of intention locks used in InnoDB (assume that transaction T has requested a lock of the indicated type on table t):
此外,InnoDB支持行级别和表级别的这样不同粒度的锁。要实现不同粒度的锁,又加了一个锁的类型叫做意向锁(intention locks)。intention lock在InnoDB里是表级锁,这表明了事务之后才能决定锁的类型(shared or exclusive)。InnoDB使用了两种类型的intention lock(假设事务T在表t上有上述锁的请求):
l Intention shared (IS): Transaction T intends to set S locks on individual rows in table t.
l Intention shared (IS): 事务T打算在表t的个别行上设置S锁。
l Intention exclusive (IX): Transaction T intends to set X locks on those rows.
l Intention exclusive (IX): 事务T打算在行上设置X锁。
For example, SELECT ... LOCK IN SHARE MODE sets an IS lock and SELECT ... FOR UPDATE sets an IX lock.
例如,SELECT ... LOCK IN SHARE MODE设置的是IS锁,SELECT ... FOR UPDATE设置的是IX锁。
The intention locking protocol is as follows:
intention lock的协议如下:
l Before a transaction can acquire an S lock on a row in table t, it must first acquire an IS or stronger lock on t.
l 在事务请求一个S锁之前,首先会要求一个IS锁或者更强的锁。
l Before a transaction can acquire an X lock on a row, it must first acquire an IX lock on t.
l 在事务请求X锁之前,首先要有IX锁。
These rules can be conveniently summarized by means of the following lock type compatibility 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.
事务被授予的锁如果和现有的锁兼容(compatible ),那就不会有冲突(conflicts )。对于和当前有冲突的锁,事务会一直等待到其被释放。如果请求的锁和当前的锁有冲突,而且还不能被授予,这就会引起死锁,并会返回一个错误。
Thus, intention locks do not block anything except full table requests (for example, LOCK TABLES ... WRITE). The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.
因此,intention lock不会阻塞任何东西除非是全表的请求(例如,LOCK TABLES ... WRITE)。IX and IS锁的主要目的是显示出有一些行记录被锁,或者将要在这些行上加锁。
Deadlock Example
The following example illustrates how an error can occur when a lock request would cause a deadlock. The example involves two clients, A and B.
下面的例子说明了当一个请求的锁是如何引起死锁的并报错的。这个例子包含了两个客户端A和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:
首先,A创建了一个表只有一行记录,然后开启了一个事务。在这个事务里,A在行上获得了一个S锁:
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:
然后,B开启了事务并试图删除这行记录:
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.
delete操作要求X锁。这个锁不能立刻被授予因为它和A所持有的S锁是不兼容的,所以它会被放置在这行记录的锁请求的队列中,B的操作也会被阻塞。
Finally, client A also attempts 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
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:
这样就发生了死锁,因为A需要X锁来删除记录。然而,这个锁又不能立刻会授予因为B已经有了一个X锁的请求而且在等待A去释放它的S锁。A所持有的S锁也不能升级到X锁因为之前B以in个请求了X锁。结果,InnoDB会为其中的一个客户端产生一个错误并释放其所持有的锁。客户端得到的错误如下:
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.
在那个时刻,其他客户的的锁请求会被授予,删除操作也会被执行。
Note
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, which is defined by LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK. 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.
InnoDB Monitor输出的LATEST DETECTED DEADLOCK章节包括一个信息说明,“TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION,”事务等待列表的数量已经达到了200的限制,这个限制是由LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK定义的。超过200个事务的等待列表将会被以死锁来对待,事务也将会试图检查被回滚的等待列表。
The same error may also occur if the locking thread must look at more than 1,000,000 locks owned by the transactions on the wait-for list. The limit of 1,000,000 locks is defined by LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK.
如果锁线程必须要查看等待列表里事务拥有的超过1,000,000个的锁,也会发生同样的错误。1,000,000个锁的限制是由LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK定义的。
14.3.2 InnoDB Record, Gap, and Next-Key Locks
InnoDB has several types of record-level locks including record locks, gap locks, and next-key locks. For information about shared locks, exclusive locks, and intention locks, see Section 14.3.1, “InnoDB Lock Modes”.
InnoDB有几种行级锁包括record lock,gap lock,和next-key lock。关于shared locks, exclusive locks, and intention locks的信息查看Section 14.3.1, “InnoDB Lock Modes”。
l Record lock: This is a lock on an index record.
l Record lock: 这种锁在索引的记录上。
l Gap lock: This is a lock on a gap between index records, or a lock on the gap before the first or after the last index record.
l Gap lock: 这种锁在索引记录之间的间隙,或者是第一个索引记录之前的间隙或者是最后一个索引记录之后的间隙。
l Next-key lock: This is a combination of a record lock on the index record and a gap lock on the gap before the index record.
l Next-key lock: 这是索引记录上的record lock和索引记录之前间隙上的gap lock的组合。
Record Locks
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 Section 14.2.5.2, “Clustered and Secondary Indexes”.
record lock总是锁在索引记录上,即使是表上没有定义索引。例如这样的一个情况,InnoDB创建了一个隐藏的clustered index,并利用这个索引做record lock。详见Section 14.2.5.2, “Clustered and Secondary Indexes”。
Next-key Locks
By default, InnoDB operates in REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 14.3.3, “Avoiding the Phantom Problem Using Next-Key Locking”).
默认情况下,InnoDB工作在REPEATABLE READ事务隔离级别以及innodb_locks_unsafe_for_binlog关闭的情况下。在这种情况下,InnoDB使用next-key lock来搜索及索引扫描,这能够阻止幻影行记录(详见Section 14.3.3, “Avoiding the Phantom Problem Using Next-Key Locking”)。
Next-key locking 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 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.
next-key lock是index-row和gap lock的组合。InnoDB以行级锁的方式进行搜索或者索引扫描,它会在索引记录上加共享或者排他锁。因此,行级锁实际指的是索引记录锁。另外,索引记录上的next-key lock还会影响索引记录之前的“间隙(gap)”。那就是说,next-key lock是index-record lock加上了索引记录之前的gap lock。如果一个session在索引记录R上有一个共享或者排他锁,其他session就不能立刻插入在R之前的间隙里插入一个新的索引记录。
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:
假设一个索引包含值10, 11, 13, and 20。这个索引的可能的next-key lock会覆盖下面的间隔,小括号表示不包含,中括号表示包含:
(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.
对于最后的间隔,next-key lock会锁住索引最大值上面的间隙以及一个大于任意一个这个索引实际值的值。最小上界(supremum)是一个真实的索引记录,所以实际上next-key lock只会锁住最大索引值后面的间隙。
Gap Locks
The next-key locking example in the previous section shows that a gap might span a single index value, multiple index values, or even be empty.
上文next-key lock的例子显示了一个gap能够跨越过单个索引值,多个索引值,甚至于是空的空间。
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:
通过唯一索引来搜索唯一行的语句是不需要gap lock的。(这种情况不包括搜索条件只包括多列多列条件的某些列;在这种情况下,gap lock还是要发生的。)例如,如果id列上有唯一索引,下面的语句只hi使用一个索引记录来查找id值是100的行记录,而不会去管其他session插入的值是否在gap里。
SELECT * FROM child WHERE id = 100;
If id is not indexed or has a nonunique index, the statement does lock the preceding gap.
如果id列上没有索引或者有的只是一个非唯一索引,那么语句会锁住前面的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上。例如,事务A持有一个shared gap lock (gap S-lock)在gap上,同时事务B也持有一个exclusive gap lock (gap X-lock)在相同的gap上。之所以这样是因为如果一个记录从索引上被purge,那么gap lock冲突是被允许的,但是不同事务在记录上持有的gap lock是必须要合并的。
Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. They do not prevent different transactions from taking gap locks on the same gap. Thus, a gap X-lock has the same effect as a gap S-lock.
gap lock在InnoDB里是“纯粹禁止的(purely inhibitive)”,这就意味着只有停止其他往gap里insert的事务。它们不会阻止不同的事务在相同的gap里去持有gap lock。因此,gap X-lock和gap S-lock的影响是相同的。
A type of gap lock called an insert intention gap lock is set by 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.
一种被称之为insert intention gap lock是在行插入之前的insert操作设置的。这种锁表示意图以这样的方式进行insert:多个事务如果它们没有insert到gap中同一个位置时,那么它们insert到相同的index gap时不需要相互等待对方。假设有索引记录值4和7。分离的事务试图分别插入索引值5和6,每个优先的在4和7之间的insert intention lock都在插入的行上包含exclusive lock,但是它们不会相互阻塞,因为行数据是不冲突的。
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.
下面的例子显示了一个事务使得优先的的insert intention lock在插入的记录上包含了exclusive lock。这个例子涉及了两个客户端,A和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:
A创建了一个表包含了两个索引记录(90 and 102),然后启动一个事务放置一个exclusive lock在ID大于100的索引记录上。这个exclusive lock包括了记录102之前的gap lock。
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.
B开启了一个事务向gap里插入一条记录。这个事务在登台获得一个exclusive lock的时候会得到一个insert intention lock。
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
To view data about the insert intention lock, run SHOW ENGINE INNODB STATUS. Data similar to the following appears under the TRANSACTIONS heading:
通过运行SHOW ENGINE INNODB STATUS可以查看insert intention lock的数据。这个数据显示在TRANSACTIONS标头的下面。
mysql> SHOW ENGINE INNODB STATUS\G
...
SHOW ENGINE INNODB STATUS
---TRANSACTION 8731, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 3, OS thread handle 0x7f996beac700, query id 30 localhost root update
INSERT INTO child (id) VALUES (101)
------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
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 ;;...
Disabling Gap Locking
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). Under these circumstances, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.
gap lock也是可以显示关闭的。如果你把事务隔离级别修改为READ COMMITTED或者开启innodb_locks_unsafe_for_binlog系统变量(这个变量已经被废弃),那么对于搜索和索引扫描gap lock就会被关闭,它只会用于外键约束的检查和重复键的检查。
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 UPDATE.
对于使用READ COMMITTED事务隔离级别或者开启innodb_locks_unsafe_for_binlog还有其他的影响:在MySQL评估WHERE条件之后不匹配的record lock将会被释放。对于UPDATE语句来说,InnoDB会执行“半一致性(semi-consistent)”读,它会返回最近commit的版本给MySQL,那么MySQL就可以决定行记录是否和UPDATE语句的WHERE条件相匹配。
14.3.3 Avoiding the Phantom Problem Using Next-Key Locking
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 SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
所谓的幻读(phantom )问题发生在一个事务里相同的查询在不同的时间点上产生了不同的结果集。例如,SELECT语句执行了两次,但是第二次执行的时候返回了一行而第一执行的时候没有记录被返回,那么这行就是”phantom”行。
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:
假设child表的id列上有一个索引,你想要读取并锁住所有值大于100的记录:
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 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.
这个查询扫描索引从第一个id大于100的记录开始。这里假设表包含值是90和102两个值的记录。如果扫描范围内在索引记录上的锁没能把gap(在这个情况下,gap是 90到102)上的insert阻拦在外的话,其他session能够insert一个新的id是101的行到表里。如果你的相同的事务里还执行了相同的SELECT语句,你会看到id是101的新行(“phantom”行)会在查询的结果集里。如果我们注意到结果集,就会发现新的phantom结果违反了事务隔离的原则:事务里读到数据不会改变。
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 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.
为了避免phantoms,InnoDB使用了next-key lock,既包括了index-row lock,又包括了gap lock。InnoDB以在索引记录上加锁的方式执行行级锁。因此,这个行级锁实际上是index-record lock。另外,索引记录上的next-record lock也影响这索引记录之前的gap。那就是说,next-key lock是一个index-record lock加上索引记录之前gap上的gap lock。如果一个session在索引记录R上持有了一个shared or exclusive lock,其他session就不能立即在索引记录R之前插入一个新的索引记录。
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.
当InnoDB扫描索引的时候,它也会锁住索引最后记录之后的gap。这只会发生在之前的例子里:为了阻止插入新的id值大于100的记录,InnoDB设置的锁包括了一个在102后面的gap上锁。
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.
你能够使用next-key lock来在你的应用里实现唯一性检查:如果你在共享模式读取里数据,而没有看到重复的行,然后你就可以安全地插入这些行。这是因为在读的过程中InnoDB已经把next-key lock放置在了这些行的后续上,这也就阻止了其他任何人同时插入重复的数据。因此,next-key lock能够使得你”锁住“表里不存在的一些东西。
Gap locking can be disabled as discussed in Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”. This may cause phantom problems because other sessions can insert new rows into the gaps when gap locking is disabled.
gap lock也能够被关闭,如 Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”所描述的。这会引起phantom问题因为其他的session在gap lock关闭的情况下能够向gap里插入新的记录。
14.3.4 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 of 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 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.
一致性读意味着InnoDB使用多版本查询在某个一个时间点上数据库快照。查询只能看到事务提交时间点之前的变化,而看不到之后以及未提交事务的变化。例外的情况是在相同事务里查询可以看到早先语句产生的变化。这个例外会引起下面的异常:如果你更新了表里的一部分数据,SELECT会看到更新数据的最新版本,但是也会看到老版本的数据。如果其他session同时更新了相同的表,这个异常也就意味着你能够阿奎那到一个从未在数据库存在的表的状态。
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.
如果事务隔离级别是REPEATABLE READ(默认的级别),所有相同事务里的一致性读会读那个在事务开始第一个能够读到的已经确认的快照。你可以通过提交当前事务然后再执行新的查询才能够得到更新版本的快照。
With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot.
在READ COMMITTED隔离级别下,事务里的每个一致性读会设置读取它自己最新的快照。
Consistent read is the default mode in which InnoDB processes 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.
在READ COMMITTED and REPEATABLE READ隔离级别下一致性读是InnoDB处理SELECT语句时默认的一种模式。一致性读不会在访问的表上设置任何的锁,因此在相同的时间里执行一致性读的时候其他session也能够修改这些表。
Suppose that you are running in the default REPEATABLE READ isolation level. When you issue a consistent read (that is, an ordinary 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.
假设你运行在默认的REPEATABLE READ隔离级别下。当你执行了一个一致性读(也就是说是一个普通的SELELCT语句),InnoDB会根据查询数据库时给你的事务一个时间点。如果其他事务在这个时间点之后删除了一行并提交了,那你就不会看到已经被删除的记录。insert和update也会以相同的方式处理。
Note
The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a DELETE or 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语句,但对DML语句来说不是必须的。如果你在事务里插入,修改了一些行数据,其他并发的REPEATABLE READ事务执行delete或者update会影响这些已提交的行,即使这个session并不能查询到它们。如果一个update或者delete其他不同事务里已提交的行数据,那这些修改对当前事务会变得可见的。例如,你可能遭遇到下弥漫的情况:
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 SELECT or START TRANSACTION WITH CONSISTENT SNAPSHOT.
你可以通过提交事务来推进时间点,然后再执行其他的SELECT或者START TRANSACTION WITH CONSISTENT SNAPSHOT。
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只有在A和B都提交了以后才能看到B所插入的数据,所以时间点推进到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:
如果你想要看到最新的数据库状态,可以使用READ COMMITTED隔离级别或者这样的锁读:
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 Section 14.3.5, “Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)”).
在READ COMMITTED隔离级别下,事务里的每一个一致性读都会设置读取它最新的快照。使用LOCK IN SHARE MODE,则会发生一个锁读:SELECT阻塞直到事务包含的最新的行结束(详见Section 14.3.5, “Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)”)。
Consistent read does not work over certain DDL statements:
在某些DDL语句上一致性读是不能工作的:
l Consistent read does not work over DROP TABLE, because MySQL cannot use a table that has been dropped and InnoDB destroys the table.
l 在DROP TABLE的时候一致性读是不工作的,因为MySQL不会使用已经被删除的表,InnoDB会破坏表的。
l Consistent read does not work over ALTER TABLE, because that statement makes a temporary copy of the original table and deletes the original table when the temporary copy is built. When you reissue a consistent read within a transaction, rows in the new table are not visible because those rows did not exist when the transaction's snapshot was taken. In this case, the transaction returns an error as of MySQL 5.6.6: ER_TABLE_DEF_CHANGED, “Table definition has changed, please retry transaction”.
l 在ALTER TABLE的时候一致性读不会工作,因为ALTER TABLE会为原是表做个一个临时拷贝,但临时拷贝建立完成之后会删除原是表。当你在事务里执行一致性读,新表里面的行是不可见的因为在获得事务快照的时候那些行是不存在的。在这种情况下,MySQL会返回一个错误ER_TABLE_DEF_CHANGED, “Table definition has changed, please retry transaction”。
The type of read varies for selects in clauses like INSERT INTO ... SELECT, UPDATE ... (SELECT), and CREATE TABLE ... SELECT that do not specify FOR UPDATE or LOCK IN SHARE MODE:
这种类型的读也适用于select子句像INSERT INTO ... SELECT, UPDATE ... (SELECT),以及没i有指定FOR UPDATE or LOCK IN SHARE MODE的CREATE TABLE ... SELECT:
l By default, InnoDB uses stronger locks and the SELECT part acts like READ COMMITTED, where each consistent read, even within the same transaction, sets and reads its own fresh snapshot.
l 默认情况下,InnoDB使用更强劲的锁,SELECT部分的动作像READ COMMITTED,每个一致性读,即使是相同事务里的,也会设置并读取它最新的快照。
l To use a consistent read in such cases, enable the innodb_locks_unsafe_for_binlog option and set the isolation level of the transaction to READ UNCOMMITTED, READ COMMITTED, or REPEATABLE READ (that is, anything other than SERIALIZABLE). In this case, no locks are set on rows read from the selected table.
l 在使用一致性读的情况有:开启innodb_locks_unsafe_for_binlog并设置事务的隔离级别为READ UNCOMMITTED,或者是用READ COMMITTED和REPEATABLE READ(也就是说,只要不是SERIALIZABLE)。在这种情况下,读取的行上不会设置锁。
14.3.5 Locking Reads (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE)
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:
如果你先查询数据然后再在相同的事务里insert或者update相关的数据,常规的SELECT语句并不能够提供足够的保护。其他的事务依然能够update或者delete你刚才查询的的相同的数据。InnoDB提供两种类型的读锁来提供额外的安全保障:
l SELECT ... LOCK IN SHARE MODE sets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.
l SELECT ... LOCK IN SHARE MODE会在读取的行上加一个共享模式的锁。其他的session能够读这些数据,但是在你当前事务提交之前它们不能修改。如果这些数据的任何部分被其他事务修改了而且还未提交,你的查询会一种等待直到那个事务结束了,然后你能够看到的也是最新的版本的数据值。
l For index records the search encounters, SELECT ... FOR UPDATE locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows. Other transactions are blocked from updating those rows, from doing SELECT ... LOCK IN SHARE MODE, or from reading the data in certain transaction isolation levels. Consistent reads ignore any locks set on the records that exist in the read view. (Old versions of a record cannot be locked; they are reconstructed by applying undo logs on an in-memory copy of the record.)
l 对于遭遇到的每个索引记录,SELECT ... FOR UPDATE会锁着行记录以及任何相关的索引条目,就如同你在这些行数据上执行了UPDATE语句。其他的事务会被修改行数据,执行SELECT ... LOCK IN SHARE MODE,或者在某个隔离级别的事务里读数据的这些操作所阻塞。但一致性读会忽略这些记录上的任何一个锁。(旧版本的记录不会被阻塞;它们会通过内存里记录拷贝中的适用的undo log进行重建。)
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.
这在处理树状结构或者图结构数据的时候会非常有用,不论是单表还是多表处理上。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.
当事务commit或者rollback的时候LOCK IN SHARE MODE and FOR UPDATE查询中所有放置的锁都会被释放。
Note
Locking of rows for update using SELECT FOR UPDATE only applies when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0. If autocommit is enabled, the rows matching the specification are not locked.
只有autocommit被关闭的时候使用SELECT FOR UPDATE才会适用于对要update的行加锁(或者通过START TRANSACTION或者设置autocommit为0都可以)。如果autocommit是开启的,指定条件的行记录不会被上锁。
Usage 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.
假设你想要向表里插入一条新的child记录,要确保child行在已经在parent表里存在了parent行。你的应用代码需要确保参考这个操作顺序的完整性。
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.
首先,要查询PARENT表来确认parent是否存在。那这样就能够安全地向CHILD表插入child行了吗?不行,因为一些其他的session可能会在你SELECT和INSERT操作的这段时间里删除了parent行,而你去完全没有意识的这点。
To avoid this potential issue, perform the SELECT using LOCK IN SHARE MODE:
要避免这种潜在的问题,可以使用LOCK IN SHARE MODE执行下面的SELECT:
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 read or write to the applicable row in the PARENT table waits until you are finished, that is, the data in all tables is in a consistent state.
在LOCK IN SHARE MODE查询返回了parent 'Jones'之后,你可以安全地添加child记录到CHILD表然后commit事务。任何事务想要试图读或者写PARENT表里相关的行都要等待直到你的事务结束,也就是说所有表里的数据都是一致状态的。
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.
另一个例子,考虑在CHILD_CODES表里用一个整型的计数列,用于为CHILD表里每个新加的child行分配一个唯一的标识符。既不要使用一致性读也不要使用共享模式读来这个计数器的当前值,因为这样才能狼数据库的两个用户看到相同的计数值。那样如果两个事务试图向CHILD表里加入相同标识的行时就会产生duplicate-key的错误。
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.
这里,LOCK IN SHARE MODE不是一个好的解决方案,因为如果两个用户在相同时间读这个计数器的值,但一个试图修改这个计数器时,它们中间至少有一个会在死锁里自己结束。
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:
为了实现读和计数器的增值,首先使用FOR UPDATE执行一个读锁,然后再增加计数器的值。例如:
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.
SELECT ... FOR UPDATE会读出最近可用的值,并在它读取的每一行上放置一个排他锁。因此,it sets the same locks a searched SQL UPDATE would set on the rows.
The preceding description is merely an example of how SELECT ... FOR UPDATE works. In MySQL, the specific task of generating a unique identifier actually can be accomplished using only a single access to the table:
前面描述的仅仅是一个SELECT ... FOR UPDATE是如何工作的例子。在MySQL里,产生唯一标识的特殊工作实际上可以通过一个单独的表访问来完成的:
UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();
The SELECT statement merely retrieves the identifier information (specific to the current connection). It does not access any table.
SELECT语句检索的仅是标识信息(特指当前的连接)。它不会访问任何表。
14.3.6 Locks Set by Different SQL Statements in InnoDB
A locking read, an UPDATE, or a DELETE generally set record locks on every index record that is scanned in the processing of the 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 Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”. The transaction isolation level also can affect which locks are set; see Section 13.3.6, “SET TRANSACTION Syntax”.
UPDATE或者DELETE会在SQL语句处理扫描的每个索引记录上行锁。而且有无WHERE条件都是没关系的。InnoDB不会牢记准确的WHERE条件,但是会知道哪些索引范围被扫描了。这里锁通常是next-key lock,可以用来立即阻塞记录之前的“gap”。然而,gap lock也是可以显示关闭的,这就会使得next-key lock不能再使用。更多的信息可以查看Section 14.3.2, “InnoDB Record, Gap, and Next-Key Locks”。事务隔离记录影响的锁可以查看Section 13.3.6, “SET TRANSACTION Syntax”。
If a secondary index is used in a search and index record locks to be set are exclusive, InnoDB also retrieves the corresponding clustered index records and sets locks on them.
如果用secondary index来搜索,而且索引记录上放置的是排他锁,InnoDB还会检索其相关的clustered index记录并在上面加锁。
Differences between shared and exclusive locks are described in Section 14.3.1, “InnoDB Lock Modes”.
共享和排他锁的区别可以查看Section 14.3.1, “InnoDB Lock Modes”。
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 unnecessarily scan many rows.
如果你的语句使用合适的索引可用,那么MySQL在处理语句的时候就必须要扫描整个表,那么表里的每行记录都会被锁,还会阻塞其他用户的向表里插入记录。创建一个好的索引让你的查询语句不必要扫描过多的行记录是非常重要的。
For SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE, locks are acquired for scanned rows, and expected to be released for rows that do not qualify for inclusion in the result set (for example, if they do not meet the criteria given in the WHERE clause). However, in some cases, rows might not be unlocked immediately because the relationship between a result row and its original source is lost during query execution. For example, in a UNION, scanned (and locked) rows from a table might be inserted into a temporary table before evaluation whether they qualify for the result set. In this circumstance, the relationship of the rows in the temporary table to the rows in the original table is lost and the latter rows are not unlocked until the end of query execution.
对于SELECT ... FOR UPDATE或者 SELECT ... LOCK IN SHARE MODE,扫描的行上也会加锁,expected to be released for rows that do not qualify for inclusion in the result set(例如,不在WHERE条件里)。然而,在一些情况下,行记录不会立刻被解锁因为the relationship between a result row and its original source is lost during query execution.例如,在UNION里,表里扫描的(被锁住的)行肯呢个会在评估它们是否符合结果集之前就被插入到了临时表里。在这种环境下,临时表里的行和原始表里行的关系会丢失,后来的行就不会被解锁直到查询执行完成。
InnoDB sets specific types of locks as follows.
InnoDB会设置如下几种类型的锁。
l SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters.
l SELECT ... FROM做的是一致性读,除非事务隔离级别是SERIALIZABLE,通常情况会读数据库的快照,不会放置锁。对于SERIALIZABLE级别,搜索会在其遭遇的索引记录上放置shared next-key locks。
l SELECT ... FROM ... LOCK IN SHARE MODE sets shared next-key locks on all index records the search encounters.
l SELECT ... FROM ... LOCK IN SHARE MODE会在搜索遭遇的所有索引记录上放置shared next-key locks。
l For index records the search encounters, SELECT ... FROM ... FOR UPDATE blocks other sessions from doing SELECT ... FROM ... LOCK IN SHARE MODE or from reading in certain transaction isolation levels. Consistent reads will ignore any locks set on the records that exist in the read view.
l 对于搜索遭遇的索引记录,SELECT ... FROM ... FOR UPDATE会阻塞其他session的SELECT ... FROM ... LOCK IN SHARE MODE操作,或者是某个事务隔离级别的读操作。一致性读会忽略记录上所有的锁。
l UPDATE ... WHERE ... sets an exclusive next-key lock on every record the search encounters.
l UPDATE ... WHERE ... 会在搜索遭遇到的每个记录上放置一个exclusive next-key lock 。
l DELETE FROM ... WHERE ... sets an exclusive next-key lock on every record the search encounters.
l DELETE FROM ... WHERE ...会在搜索遭遇的每个记录上放置一个an exclusive next-key lock。
l INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
l INSERT会在插入的行上放置一个exclusive lock。这个锁是一个索引记录锁,不是next-key lock(也就是说不存在gap lock),它不会阻拦其他session像插入的记录之前的gap里再插入数据。
Prior to inserting the row, a type of gap lock called an insertion intention gap lock is set. 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 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.
在插入记录之前,会放置一个称之为insertion intention gap lock的gap lock。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. 假设有索引记录值4和7。假设不同的事务试图使用insert intention lock(也包含在插入的行的排他锁)向4和7之间的gap插入5和6,但是它们不会相互阻塞因为这些行是不冲突的。
If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row. Suppose that an InnoDB table t1 has the following structure:
如果发生了重复键的错误,可以在重复索引记录上放置一个共享锁。但是这样做也会产生死锁。因为如果其他session已经持有了排他锁对歌session再试图插入相同的就会造成死锁。这会发生在其他session删除行的时候。假设有一个InnoDB表,结果如下:
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
Now suppose that three sessions perform the following operations in order:
现在假设有三个session依次执行下面的操作:
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 by session 1 acquires an exclusive lock for the row. The operations by sessions 2 and 3 both result in a duplicate-key error and they both request a shared lock for the row. When session 1 rolls back, it releases its exclusive lock on the row and the queued shared lock requests for sessions 2 and 3 are granted. At this point, sessions 2 and 3 deadlock: Neither can acquire an exclusive lock for the row because of the shared lock held by the other.
第一个操作session 1在行上获得一个排他锁。session 2和3都会获得重复键的错误,它们都会在行上获得一个共享锁。当session 1会滚了,它释放了在行上的排他锁,session 2和3请求的共享锁队列会被授予。在这个时间上,session 2和3相互死锁:谁都不能获得改行上的排他锁因为对方都持有了共享锁。
A similar situation occurs if the table already contains a row with key value 1 and three sessions perform the following operations in order:
一个相似情况也会发生:如果表已经包含了一个值1,三个session按顺序执行下面的操作:
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 by session 1 acquires an exclusive lock for the row. The operations by sessions 2 and 3 both result in a duplicate-key error and they both request a shared lock for the row. When session 1 commits, it releases its exclusive lock on the row and the queued shared lock requests for sessions 2 and 3 are granted. At this point, sessions 2 and 3 deadlock: Neither can acquire an exclusive lock for the row because of the shared lock held by the other.
第一个操作session 1在行上获得一个排他锁。session 2和3都会获得重复键的错误,它们都会在行上获得一个共享锁。当session 1提交了,它会释放它在行上的排他锁,session 2和3请求的共享锁队列将被授予。在这个时间点上,session 2和3相互死锁:谁都不能获得改行上的排他锁因为对方都持有了共享锁。
l INSERT ... ON DUPLICATE KEY UPDATE differs from a simple INSERT in that an exclusive next-key lock rather than a shared lock is placed on the row to be updated when a duplicate-key error occurs.
l INSERT ... ON DUPLICATE KEY UPDATE不同于简单的INSERT,当发生duplicate-key错误的时候它在要更新的行上放置的是exclusive next-key lock而不是共享锁。
l REPLACE is done like an INSERT if there is no collision on a unique key. Otherwise, an exclusive next-key lock is placed on the row to be replaced.
l REPLACE会和INSERT一样处理如果唯一键冲突的话。否则,会在更要替换的行上放置exclusive next-key lock。
l INSERT INTO T SELECT ... FROM S WHERE ... sets an exclusive index record lock (without a gap lock) on each row inserted into T. If the transaction isolation level is READ COMMITTED, or innodb_locks_unsafe_for_binlog is enabled and the transaction isolation level is not SERIALIZABLE, InnoDB does the search on S as a consistent read (no locks). Otherwise, InnoDB sets shared next-key locks on rows from S. InnoDB has to set locks in the latter case: In roll-forward recovery from a backup, every SQL statement must be executed in exactly the same way it was done originally.
l INSERT INTO T SELECT ... FROM S WHERE ...会在每个插入到T的行上放置exclusive index record lock(没有gap lock)。如果事务隔离级别是READ COMMITTED,或者innodb_locks_unsafe_for_binlog开启事务隔离级别不是SERIALIZABLE,InnoDB会在S上做一致性读(没有锁)的搜索操作。否则,InnoDB会在来自于S的行上放置shared next-key locks。对于后者InnoDB将会放置锁:在从备份的roll-forward recovery,每个SQL语句都会明确从其原始的方式执行。
CREATE TABLE ... SELECT ... performs the SELECT with shared next-key locks or as a consistent read, as for INSERT ... SELECT.
CREATE TABLE ... SELECT ...在执行SELECT的时候会使用shared next-key locks,或者会使用一致性读,如同INSERT ... SELECT。
When a SELECT is used in the constructs REPLACE INTO t SELECT ... FROM s WHERE ... or UPDATE t ... WHERE col IN (SELECT ... FROM s ...), InnoDB sets shared next-key locks on rows from table s.
当SELECT被用于构造REPLACE INTO t SELECT ... FROM s WHERE ...或者UPDATE t ... WHERE col IN (SELECT ... FROM s ...)的时候,InnoDB会在来自于表s的行上放置shared next-key lock。
l While initializing a previously specified AUTO_INCREMENT column on a table, InnoDB sets an exclusive lock on the end of the index associated with the AUTO_INCREMENT column. In accessing the auto-increment counter, InnoDB uses a specific AUTO-INC table lock mode where the lock lasts only to the end of the current SQL statement, not to the end of the entire transaction. Other sessions cannot insert into the table while the AUTO-INC table lock is held; see Section 14.3, “InnoDB Transaction Model and Locking”.
l 当初始化先前指定的AUTO_INCREMENT列的时候,InnoDB会在AUTO_INCREMENT列的相关索引的结尾放置一个排他锁。在访问自增计数器的时候,InnoDB使用指定AUTO-INC table lock模式,这种锁只会持续到当前SQL语句的结束,而不是整个事务的结束。当持有AUTO-INC table lock的时候其他session是不能够向表里插入数据的;详见Section 14.3, “InnoDB Transaction Model and Locking”。
InnoDB fetches the value of a previously initialized AUTO_INCREMENT column without setting any locks.
InnoDB获取先前初始化AUTO_INCREMENT列的值而不需要设置任何的锁。
l If a FOREIGN KEY constraint is defined on a table, any insert, update, or delete that requires the constraint condition to be checked sets shared record-level locks on the records that it looks at to check the constraint. InnoDB also sets these locks in the case where the constraint fails.
l 如果在表上定义了外键约束,任何insert,update,或者delete请求的约束条件都要设置shared record-level lock来做检查。即使约束失败InnoDB还是会设置这些锁的。
l LOCK TABLES sets table locks, but it is the higher MySQL layer above the InnoDB layer that sets these locks. InnoDB is aware of table locks if innodb_table_locks = 1 (the default) and autocommit = 0, and the MySQL layer above InnoDB knows about row-level locks.
l LOCK TABLES会放置表锁,但是这些锁是放在高于InnoDB层的MySQL层上的。如果innodb_table_locks = 1 (the default)以及autocommit = 0,InnoDB放置的是表锁,InnoDB上面额MySQL层会认为是行级锁。
Otherwise, InnoDB's automatic deadlock detection cannot detect deadlocks where such table locks are involved. Also, because in this case the higher MySQL layer does not know about row-level locks, it is possible to get a table lock on a table where another session currently has row-level locks. However, this does not endanger transaction integrity, as discussed in Section 14.3.8, “Deadlock Detection and Rollback”. See also Section 14.6.7, “Limits on InnoDB Tables”.
否则,InnoDB's automatic deadlock detection cannot detect deadlocks where such table locks are involved。因为这种情况下更高的MySQL层不知道行级锁,当其他session使用了行级锁那有可能会使用表锁。然而,这不会危害到事务的完整性,如果Section 14.3.8, “Deadlock Detection and Rollback”所描述的。或者查看Section 14.6.7, “Limits on InnoDB Tables”。
14.3.7 Implicit Transaction Commit and Rollback
By default, MySQL starts the session for each new connection with autocommit mode 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 Section 14.19.4, “InnoDB Error Handling”.
默认情况下,MySQL为每个新连接开始的session使用的是autocommit模式,如果没有返回错误的话MySQL会在每个SQL语句后面进行提交。如果语句报错,会根据错误来决定执行commit或者rollback。详见Section 14.19.4, “InnoDB Error Handling”。
If a session that has autocommit disabled ends without explicitly committing the final transaction, MySQL rolls back that transaction.
如果一个session是autocommit关闭的,而且在事务的最后也没有显式地commit,那么MySQL会回滚这个事务。
Some statements implicitly end a transaction, as if you had done a COMMIT before executing the statement. For details, see Section 13.3.3, “Statements That Cause an Implicit Commit”.
一些语句会隐式地结束事务,就如在执行这些语句之前执行了commit一样。详见Section 13.3.3, “Statements That Cause an Implicit Commit”。
14.3.8 Deadlock Detection and Rollback
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会自动发现事务死锁并回滚一个事务或者从死锁中终止事务。InnoDB会试图选哪小的事务来进行回滚,事务的大小决定了insert,update或者delete的行的数量。
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 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.
如果innodb_table_locks = 1 (the default) and autocommit = 0的时候InnoDB is aware of table locks,但它上面的MySQL层会认为是行级锁。否则,MySQL LOCK TABLES语句设置的表锁或者InnoDB之外其他存储引擎设置的锁中,InnoDB会探测不到死锁。通过设置innodb_lock_wait_timeout系统变量可以解决这个问题。
When InnoDB performs a complete rollback of a transaction, all locks set by the transaction are released. However, if just a single SQL statement is rolled back as a result of an error, some of the locks set by the statement may be preserved. This happens because InnoDB stores row locks in a format such that it cannot know afterward which lock was set by which statement.
当InnoDB执行了一个事务的完整的回滚,这个事务锁放置的所有锁都会被释放。然而,如果单个SQL语句因为结果错误而进行的回滚,这个语句放置的一些锁将会继续被放置着。之所以发生这种情况是因为InnoDB存储的行锁的方式不会知道这个语句以后放置的锁。
If a SELECT calls a stored function in a transaction, and a statement within the function fails, that statement rolls back. Furthermore, if ROLLBACK is executed after that, the entire transaction rolls back.
如果事务里的SELECT使用了存储函数,而且函数调用失败了,这个语句会回滚。此外,如果在那子厚执行了rollback,那整个事务都会回滚。
For techniques to organize database operations to avoid deadlocks, see Section 14.3.9, “How to Cope with Deadlocks”.
对于如何组织数据库操作来避免死锁,可以查看Section 14.3.9, “How to Cope with Deadlocks”。
14.3.9 How to Cope with Deadlocks
This section builds on the conceptual information about deadlocks in Section 14.3.8, “Deadlock Detection and Rollback”. It explains how to organize database operations to minimize deadlocks and the subsequent error handling required in applications.
这章构建在Section 14.3.8, “Deadlock Detection and Rollback”所讲述的死锁的概念信息上。这里讲述了如何组织数据操作最小化死锁的可能性,以及在应用中如何处理随后的错误。
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.
InnoDB使用自动的行级锁。你可以在insert或者delete的事务里得到死锁事件。这是因为这些操作不是真正的“原子性的(atomic)”;它们会自动锁住插入或者修改的行的索引记录。
You can cope with deadlocks and reduce the likelihood of their occurrence with the following techniques:
你可以使用下面的技术来处理死锁以及降低其发生的可能性:
l At any time, issue the SHOW ENGINE INNODB STATUS command to determine the cause of the most recent deadlock. That can help you to tune your application to avoid deadlocks.
l 在任何时候,执行SHOW ENGINE INNODB STATUS命令来查看最近死锁发生的原因。这能够帮助你调整应用程序来避免死锁。
l If frequent deadlock warnings cause concern, collect more extensive debugging information by enabling the innodb_print_all_deadlocks configuration option. Information about each deadlock, not just the latest one, is recorded in the MySQL error log. Disable this option when you are finished debugging.
l 如果频繁发生死锁警告,可以开启innodb_print_all_deadlocks配置参数来收集更广泛的debug信息。这样每个死锁的信息,而不仅是最近一个的,都会被记录在MySQL的错误日志里。在完成debug后记得关闭这个参数。
l Always be prepared to re-issue a transaction if it fails due to deadlock. Deadlocks are not dangerous. Just try again.
l 如果因为死锁而发生的错误,要总是准备好再执行一次。死锁没有危险,尽管重试。
l Keep transactions small and short in duration to make them less prone to collision.
l 短小的事务发生碰撞的可能行更小。
l Commit transactions immediately after making a set of related changes to make them less prone to collision. In particular, do not leave an interactive mysql session open for a long time with an uncommitted transaction.
l 立刻提交事务能够减少碰撞额可能性。特别是,不在在未提交的事务里长时间使用交互式的session。
l If you use locking reads (SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE), try using a lower isolation level such as READ COMMITTED.
l 如果你使用读锁(SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE),可以尝试使用更低隔离级别例如READ COMMITTED。
l When modifying multiple tables within a transaction, or different sets of rows in the same table, do those operations in a consistent order each time. Then transactions form well-defined queues and do not deadlock. For example, organize database operations into functions within your application, or call stored routines, rather than coding multiple similar sequences of INSERT, UPDATE, and DELETE statements in different places.
l 如果在事务里修改多个表,或者同个表里的不同的数据集,那就每次以一致的顺序去执行这些操作。这些定义明确顺序的事务会产生死锁。例如,在应用里把原始的数据库操作放在一个函数里,或者使用存储程序,而不是在代码里以不同的顺序执行INSERT,UPDATE,和DELETE语句。
l Add well-chosen indexes to your tables. Then your queries need to scan fewer index records and consequently set fewer locks. Use EXPLAIN SELECT to determine which indexes the MySQL server regards as the most appropriate for your queries.
l 添加选择性的好的索引。这样你的查询就只需要扫描更少的索引记录,从而设置更少的锁。使用EXPLAIN SELECT来确认查询使用了哪个索引。
l Use less locking. If you can afford to permit a SELECT to return data from an old snapshot, do not add the clause FOR UPDATE or LOCK IN SHARE MODE to it. Using the READ COMMITTED isolation level is good here, because each consistent read within the same transaction reads from its own fresh snapshot.
l 使用更少的锁。如果你能够承受旧版本的数据,那就不要使用FOR UPDATE或者LOCK IN SHARE MODE子句了。在这里使用READ COMMITTED各级级别是不错的选择,因为相同事务里每个一致性读都只会读它自身最新的快照。
l If nothing else helps, serialize your transactions with table-level locks. The correct way to use LOCK TABLES with transactional tables, such as InnoDB tables, is to begin a transaction with SET autocommit = 0 (not START TRANSACTION) followed by LOCK TABLES, and to not call UNLOCK TABLES until you commit the transaction explicitly. For example, if you need to write to table t1 and read from table t2, you can do this:
l 如果没有其他的帮助,使用表级别的锁串行化你的事务。正确的方式是在事务表(如InnoDB表)上使用LOCK TABLES,使用SET autocommit = 0(不是START TRANSACTION)开启一个事务随后再跟着LOCK TABLES,不要调用UNLOCK TABLES直到你显式地commit事务。例如,你需要向t1表里写数据,从t2表里读数据,可以这么做:
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;
Table-level locks prevent concurrent updates to the table, avoiding deadlocks at the expense of less responsiveness for a busy system.
表级别的锁会阻止其他对表的并发更新,以更少响应能力的代价避免死锁。
l Another way to serialize transactions is to create an auxiliary “semaphore” table that contains just a single row. Have each transaction update that row before accessing other tables. In that way, all transactions happen in a serial fashion. Note that the InnoDB instant deadlock detection algorithm also works in this case, because the serializing lock is a row-level lock. With MySQL table-level locks, the timeout method must be used to resolve deadlocks.
l 另一个串行化事务的方式是创建一个辅助的“semaphore”表只包含一行记录。每个事务在访问其他表之前都要更新这行记录。这种方式下,所有的事务都是串行的。要注意的是InnoDB探测死锁的算法也是以这种方式工作的,因为串行锁是一个行级锁。如果使用MySQL的表级锁,那还要使用timeout的方式来解决死锁。
MySQL 5.6 Reference Manual-14.3 InnoDB Transaction Model and Locking的更多相关文章
- MySQL :: MySQL 5.0 Reference Manual :: 14.4 The MEMORY (HEAP) Storage Engine
MySQL :: MySQL 5.0 Reference Manual :: 14.4 The MEMORY (HEAP) Storage Engine The MEMORY (HEAP) Stora ...
- MySQL :: MySQL 8.0 Reference Manual :: B.6.4.3 Problems with NULL Values https://dev.mysql.com/doc/refman/8.0/en/problems-with-null.html
MySQL :: MySQL 8.0 Reference Manual :: B.6.4.3 Problems with NULL Values https://dev.mysql.com/doc/r ...
- MySQL 5.7 Reference Manual :: 4.5.4 mysqldump & mysql — Database Backup & Restore Program
MySQL :: MySQL 5.7 Reference Manual :: 4.5.4 mysqldump — A Database Backup Programhttps://dev.mysql. ...
- [MySQL Reference Manual]14 InnoDB存储引擎
14 InnoDB存储引擎 14 InnoDB存储引擎 14.1 InnoDB说明 14.1.1 InnoDB作为默认存储引擎 14.1.1.1 存储引擎的趋势 14.1.1.2 InnoDB变成默认 ...
- 【MySQL 5.7 Reference Manual】15.4.2 Change Buffer(变更缓冲)
15.4.2 Change Buffer(变更缓冲) The change buffer is a special data structure that caches changes to se ...
- MySQL 5.7 Reference Manual】15.4.2 Change Buffer(变更缓冲)
15.4.2 Change Buffer(变更缓冲) The change buffer is a special data structure that caches changes to se ...
- [MySQL Reference Manual] 8 优化
8.优化 8.优化 8.1 优化概述 8.2 优化SQL语句 8.2.1 优化SELECT语句 8.2.1.1 SELECT语句的速度 8.2.1.2 WHERE子句优化 8.2.1.3 Range优 ...
- MySQL 5.6 Reference Manual-14.6 InnoDB Table Management
14.6 InnoDB Table Management 14.6.1 Creating InnoDB Tables 14.6.2 Moving or Copying InnoDB Tables to ...
- MySQL 5.6 Reference Manual-14.1 Introduction to InnoDB
14.1 Introduction to InnoDB 14.1.1 InnoDB as the Default MySQL Storage Engine 14.1.2 Checking InnoDB ...
随机推荐
- Linux常见英文报错中文翻译
Linux常见英文报错中文翻译(菜鸟必知) 1.command not found 命令没有找到 2.No such file or directory 没有这个文件或目录 3.Permission ...
- [Kernel参数]----/etc/sysctl.conf
修改sysctl方法 方法一:修改/proc下内核参数文件内容 直接修改内核参数ip_forward对应在/proc下的文件/proc/sys/net/ipv4/ip_forward.用下面命令查看i ...
- nyoj4-ASCII码排序
ASCII码排序 时间限制:3000 ms | 内存限制:65535 KB 难度:2 描述 输入三个字符(可以重复)后,按各字符的ASCII码从小到大的顺序输出这三个字符. 输入 第一行输入一个数N, ...
- Linux思维导图之文本工具、正则
习题巩固: 1,找出的ifconfig"网卡名"命令结果中本机的IPv4的地址 2,查出分区空间使用率的最大百分比值 3,查出用户UID最大值的用户名,UID及壳类型 4,查出的/ ...
- mongodb集群搭建(分片+副本)
原文地址:https://www.cnblogs.com/ityouknow/p/7344005.html 相关概念 先来看一张图: 从图中可以看到有四个组件:mongos.config server ...
- python第十周:进程、协程、IO多路复用
多进程(multiprocessing): 多进程的使用 multiprocessing是一个使用类似于线程模块的API支持产生进程的包. 多处理包提供本地和远程并发,通过使用子进程而不是线程有效地侧 ...
- qsort快速排序
C库函数qsort七种使用方法示例 七种qsort排序方法<本文中排序都是采用的从小到大排序> 一.对int类型数组排序C++ / C 代码 int num[100]; Sample: i ...
- VS的代码管理工具
参考文章 可以从中知道修改代码段是要打开相关路径下的文档来修改 官方下载地址 百度云盘 如果项目中国有一些代码重复率非常高,可以把这些代码做成snippet代码段,简化成快捷键命令,加速开发
- [bzoj2259][Oibh]新型计算机_Dijkstra
新型计算机 bzoj-2259 Oibh 题目大意:给定一个n个数的数列,第i个数为a[i],更改第i个数至x的代价为|x-a[i]|.求最小代价,使得:读入一个数s1后,向后连着读s1个数,然后如s ...
- 洛谷 P2412 查单词
P2412 查单词 题目背景 滚粗了的HansBug在收拾旧英语书,然而他发现了什么奇妙的东西. 题目描述 udp2.T3如果遇到相同的字符串,输出后面的 蒟蒻HansBug在一本英语书里面找到了一个 ...