原文地址:https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
14.5.2.3 一致性非锁定读取
一致性读取意味着InnoDB使用多版本化在某个时间点向查询呈现数据库的快照。该查询可以查看在该时间点之前提交的事务所做的更改,并且没有被后来的或未提交的事务修改。此规则的异常是该查询查看同一事务中较早的语句所做的更改。此异常会导致以下异常:如果您更新表中的某些行,则SELECT会看到更新行的最新版本,但它也可能会看到任何行的旧版本。如果其他会话同时更新同一个表,异常意味着您可能会看到该表处于数据库中从未存在的状态。
如果事务隔离级别为REPEATABLE READ(默认级别),则同一事务内的所有一致读取将读取该事务中第一次读取所创建的快照。您可以通过提交当前事务并在发出新查询之后为您的查询获得更新的快照。
通过READ COMMITTED隔离级别,事务中的每个一致读取都会设置并读取其自己的新快照。
一致性读取是InnoDB在READ COMMITTED和REPEATABLE READ隔离级别中处理SELECT语句的默认模式。一致性读取不会在它访问的表上设置任何锁,因此其他会话可以自由修改这些表,同时在表上执行一致性读取。
假设您正在运行默认的REPEATABLE READ隔离级别。当您发出一致性读取(即普通的SELECT语句)时,InnoDB会为您的事务提供一个根据您的查询查看数据库的时间点。如果另一个事务删除了一行并在分配了时间点后提交,则不会看到该行已被删除。插入和更新的处理方式相似。
注意
数据库状态的快照适用于事务中的SELECT语句,而不一定适用于DML语句。如果插入或修改某些行然后提交该事务,则从另一个并发REPEATABLE READ事务发出的DELETE或UPDATE语句可能会影响那些刚刚提交的行,即使会话无法查询它们。如果事务更新或删除由不同事务提交的行,则这些更改对当前事务变得可见。例如,您可能会遇到如下情况:
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.
您可以通过提交事务来提前您的时间点,然后再进行另一个SELECT或START TRANSACTION WITH CONSISTENT SNAPSHOT。
这称为多版本并发控制。
在下面的例子中,只有当B提交了插入并且A已经提交时,会话A才看到由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 |
---------------------
如果要查看数据库的“最新”状态,请使用READ COMMITTED隔离级别或锁定读取:
SELECT * FROM t FOR SHARE;
通过READ COMMITTED隔离级别,事务中的每个一致读取都会设置并读取其自己的新快照。在LOCK IN SHARE MODE中,会发生锁定读取:SELECT会阻塞,直到包含最新行的事务结束(请参见第14.5.2.4节“锁定读取”)。
一致性读取不适用于某些DDL语句:
- 一致性读取不能在DROP TABLE上工作,因为MySQL不能使用已经被删除的表并且InnoDB销毁该表。
- 一致性读取操作不能工作在ALTER TABLE上,因为该语句会生成原始表的临时副本,并在构建临时副本后删除原始表。在事务中重新发起一致性读取时,新表中的行不可见,因为在执行事务快照时,这些行不存在。在这种情况下,事务返回错误:ER_TABLE_DEF_CHANGED,“表定义已更改,请重试事务”。
读取的类型因不指定FOR UPDATE或LOCK IN SHARE MODE的INSERT INTO … SELECT,UPDATE …(SELECT)和CREATE TABLE …等子句中的select而异:
- 默认情况下,InnoDB使用更强大的锁,SELECT部分的行为类似于READ COMMITTED,即使在同一事务中,每个一致性读取也会设置并读取其自己的新快照。
- 要在这种情况下使用一致读取,请启用innodb_locks_unsafe_for_binlog选项并将事务的隔离级别设置为READ UNCOMMITTED,READ COMMITTED或REPEATABLE READ(即SERIALIZABLE以外的任何其他)。在这种情况下,从所选表格读取的行上不设置锁。