14.7.2.3 一致的非锁定读取
consistent readtable 示InnoDB
使用多版本控制在某个时间点向查询呈现数据库快照。该查询将看到在该时间点之前提交的事务所做的更改,而看不到以后或未提交的事务所做的更改。该规则的 exception 是查询可以看到同一事务中较早的语句所做的更改。此异常导致以下异常:如果更新 table 中的某些行,则SELECT将看到已更新行的最新版本,但也可能会看到任何行的旧版本。如果其他会话同时更新同一张 table,则异常意味着您可能会以数据库中不存在的状态查看该 table。
如果事务isolation level为REPEATABLE READ(默认级别),则同一事务中的所有一致读取将读取由该事务中的第一个此类读取构建的快照。您可以通过提交当前事务并在此之后发出新查询来获取查询的更新快照。
使用READ COMMITTED隔离级别,事务中的每个一致读取都会设置并读取其自己的新快照。
一致读取是InnoDB
处理READ COMMITTED和REPEATABLE READ隔离级别的SELECT语句的默认模式。一致读取不会在它访问的 table 上设置任何锁,因此其他会话可以自由地在对 table 执行一致读取的同时修改这些 table。
假设您以默认的REPEATABLE READ隔离级别运行。当您发出一致的读取(即普通的SELECT语句)时,InnoDB
为您的事务提供一个时间点,根据该时间点您的查询可以看到数据库。如果另一个事务删除了一行并在分配了时间点后提交,则看不到该行已被删除。插入和更新的处理方式相似。
Note
数据库状态的快照适用于事务中的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或开始使用一致的快照进行 Transaction来提前时间。
这称为多版本并发控制。
在以下示例中,会话 A 仅在 B 提交了插入并且 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隔离级别或locking read:
SELECT * FROM t FOR SHARE;
使用READ COMMITTED隔离级别,事务中的每个一致读取都会设置并读取其自己的新快照。使用LOCK IN SHARE MODE
时,将发生锁定读取:SELECT
阻塞,直到包含最新行的事务结束(请参阅第 14.7.2.4 节“锁定读取”)。
一致读取不适用于某些 DDL 语句:
-
一致读取无法在DROP TABLE上进行,因为 MySQL 无法使用已删除的 table,而
InnoDB
会破坏该 table。 -
一致读取在ALTER TABLE上不起作用,因为该语句将创建原始 table 的临时副本,并在构建该临时副本时删除该原始 table。当您在事务内重新发出一致的读取时,新 table 中的行不可见,因为在获取事务快照时这些行不存在。在这种情况下,事务返回错误:ER_TABLE_DEF_CHANGED,“table 定义已更改,请重试事务”。
对于未指定FOR UPDATE
或LOCK IN SHARE MODE
的插入...选择,更新...(选择)和创建 table...选择之类的 select 子句,读取的类型有所不同:
-
默认情况下,
InnoDB
使用更强的锁定,而SELECT部分的作用类似于READ COMMITTED,即使在同一事务中,每个一致的读取也会设置并读取自己的新快照。 -
要在这种情况下使用一致的读取,请启用innodb_locks_unsafe_for_binlog选项,并将事务的隔离级别设置为READ UNCOMMITTED,READ COMMITTED或REPEATABLE READ(即SERIALIZABLE以外的任何值)。在这种情况下,不会对从所选 table 读取的行设置锁定。