一致性读取意味着 InnoDB
使用多版本控制在某个时间点向查询呈现数据库的快照。 查询可以看到在该时间点之前提交的事务所做的更改,而看不到之后或未提交的事务所做的更改。 此规则的例外是查询可以看到同一事务中先前语句所做的更改。 此例外会导致以下异常:如果您更新了表中的一些行,则 SELECT
会看到已更新行的最新版本,但它也可能会看到任何行的旧版本。 如果其他会话同时更新同一个表,则异常意味着您可能会看到数据库中从未存在过的表状态。
如果事务 隔离级别 为 REPEATABLE READ
(默认级别),则同一事务中的所有一致性读取都将读取由该事务中的第一个此类读取建立的快照。 您可以通过提交当前事务并在之后发出新查询来为您的查询获取更新的快照。
使用 READ COMMITTED
隔离级别,事务中的每次一致性读取都设置并读取其自己的新快照。
一致性读取是 InnoDB
处理 SELECT
语句的默认模式,用于 READ COMMITTED
和 REPEATABLE READ
隔离级别。 一致性读取不会在其访问的表上设置任何锁,因此其他会话可以在对表执行一致性读取的同时自由修改这些表。
假设您在默认的 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
来提前您的时间点。
这称为 多版本并发控制。
在以下示例中,会话 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
隔离级别或锁定读取。
SELECT * FROM t FOR SHARE;
使用 READ COMMITTED
隔离级别,事务中的每次一致性读取都会设置并读取其自身的最新快照。使用 FOR SHARE
,则会改为进行锁定读取:SELECT
会阻塞,直到包含最新行的的事务结束(请参阅第 17.7.2.4 节“锁定读取”)。
一致性读取不适用于某些 DDL 语句。
一致性读取不适用于
DROP TABLE
,因为 MySQL 无法使用已删除的表,并且InnoDB
会销毁该表。一致性读取不适用于会创建原始表的临时副本并在构建临时副本时删除原始表的
ALTER TABLE
操作。当您在事务中重新发出一致性读取时,新表中的行不可见,因为这些行在创建事务快照时不存在。在这种情况下,事务会返回错误:ER_TABLE_DEF_CHANGED
,““表定义已更改,请重试事务””。
对于未指定 FOR UPDATE
或 FOR SHARE
的 INSERT INTO ... SELECT
、UPDATE ... (SELECT)
和 CREATE TABLE ... SELECT
等子句中的选择,读取类型会有所不同。
默认情况下,
InnoDB
对这些语句使用更强的锁,并且SELECT
部分的行为类似于READ COMMITTED
,其中每次一致性读取(即使在同一事务中)都会设置并读取其自身的最新快照。要在这种情况下执行非锁定读取,请将事务的隔离级别设置为
READ UNCOMMITTED
或READ COMMITTED
,以避免在从所选表读取的行上设置锁。