#### 15.7.2.3 一致的非锁定读取

[](<>)[](<>)

一种[一致读](glossary.html#glos_consistent_read)意思是`InnoDB`使用多版本控制向查询呈现数据库在某个时间点的快照。查询会看到在该时间点之前提交的事务所做的更改，以及稍后或未提交的事务所做的更改。此规则的例外是查询会看到同一事务中先前语句所做的更改。此异常会导致以下异常：如果更新表中的某些行，则[`选择`](select.html)看到更新行的最新版本，但它也可能看到任何行的旧版本。如果其他会话同时更新同一个表，则异常意味着您可能会看到该表处于数据库中从未存在过的状态。

如果交易[隔离级别](glossary.html#glos_isolation_level)是[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read)（默认级别），同一事务中的所有一致性读取都会读取该事务中第一次此类读取所建立的快照。您可以通过提交当前事务并在之后发出新查询来获得查询的更新快照。

和[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)隔离级别，事务中的每个一致读取设置并读取自己的新快照。

一致读取是默认模式，其中`InnoDB`流程[`选择`](select.html)中的陈述[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)和[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read)隔离级别。一致读取不会对其访问的表设置任何锁定，因此其他会话可以在对表执行一致读取的同时自由修改这些表。

假设您在默认情况下运行[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read)隔离级别。当您发出一致的读取（即普通[`选择`](select.html)陈述），`InnoDB`为您的事务提供一个时间点，根据该时间点您的查询查看数据库。如果另一个事务在分配您的时间点后删除了一行并提交，您不会看到该行已被删除。插入和更新的处理方式类似。

笔记

数据库状态的快照适用于[`选择`](select.html)交易中的陈述，不一定要[DML](glossary.html#glos_dml)陈述。如果您插入或修改某些行，然后提交该事务，则[`删除`](delete.html)要么[`更新`](update.html)从另一个并发发出的声明`可重复阅读`事务可能会影响那些刚刚提交的行，即使会话无法查询它们。如果一个事务确实更新或删除了由不同事务提交的行，那么这些更改对当前事务是可见的。例如，您可能会遇到如下情况：

```
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.html)要么[`使用一致的快照开始交易`](commit.html).

这称为多版本并发控制。

在以下示例中，会话 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    |
           ---------------------
```

如果要查看数据库的“最新”状态，请使用[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)隔离级别或[锁定读取](glossary.html#glos_locking_read)：

```
SELECT * FROM t FOR SHARE;
```

和[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)隔离级别，事务中的每个一致读取设置并读取自己的新快照。和`分享`，而是发生锁定读取：A`选择`阻塞直到包含最新行的事务结束（参见[第 15.7.2.4 节，“锁定读取”](innodb-locking-reads.html)）。

一致性读取不适用于某些 DDL 语句：

-   一致的读取无法结束[`删除表`](drop-table.html), 因为 MySQL 不能使用已经被删除的表并且`InnoDB`破坏桌子。

-   一致的读取无法结束[`更改表`](alter-table.html)制作原始表的临时副本并在构建临时副本时删除原始表的操作。当您在事务中重新发出一致读取时，新表中的行不可见，因为在获取事务的快照时这些行不存在。在这种情况下，事务返回错误：[`ER_TABLE_DEF_CHANGED`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_table_def_changed), “表定义已更改，请重试事务”。

    读取类型因子句中的选择而异，例如[`插入...选择`](insert.html), [`更新...（选择）`](update.html)， 和[`创建表...选择`](create-table.html)没有指定的`更新`要么`分享`：

-   默认，`InnoDB`对这些语句使用更强的锁，并且[`选择`](select.html)部分行为就像[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)，即使在同一个事务中，每次一致读取都会设置并读取自己的新快照。

-   要在这种情况下执行非锁定读取，请将事务的隔离级别设置为[`阅读未提交`](innodb-transaction-isolation-levels.html#isolevel_read-uncommitted)要么[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)以避免对从选定表中读取的行设置锁定。
