17.7.2.3 一致的非锁定读取
一致的读取(consistent read)意味着 InnoDB 使用多版本控制来向查询提供数据库在某个时间点的快照。查询看到由已经提交的事务所做的更改,但不见由未提交或晚于该时间点的事务所做的更改。除此之外,如果查询是同一个事务中的其他语句,那么它将看到早先语句所做的更改。这导致了以下异常:如果你更新某个表的行,一次 SELECT 可能会看到最新版本的更新行,但也可能会看到其他行的更早版本。如果其他会话同时更新同一个表,那么异常意味着你可能会看到从来不存在于数据库中的表状态。
如果事务的 隔离级别 是 REPEATABLE READ(默认级别),那么同一个事务中的所有一致读取都将读取由该事务中第一个这样的读取所建立的快照。你可以通过提交当前事务并在那之后发出新的查询来获取 fresher 快照。
使用 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 |
---------------------
如果您想查看数据库的“最新”状态,使用 either 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,“表定义已更改,请重新提交事务”。
读取类型在以下类似语句中不同:INSERT INTO ... SELECT、UPDATE ... (SELECT)和CREATE TABLE ... SELECT,这些语句不指定FOR UPDATE或FOR SHARE:
-
默认情况下,
InnoDB使用更强的锁定对这些语句和SELECT部分像READ COMMITTED一样,每个一致读取,甚至在同一个事务中,也会设置和读取自己的新快照。 -
在这种情况下,为了执行非锁定读取,可以将事务的隔离级别设置为
READ UNCOMMITTED或READ COMMITTED,以避免对从所选表中读取的行设置锁定。