14.5.2.3 一致性非阻塞读
原文:https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
14.5.2.3 Consistent Nonlocking Reads
一致性读意味着InnoDB引擎使用多版本控制(multi-versioning)提供查询在某一时间点快照的能力。这个查询能够看到在这个时间点之前提交的数据,但是不能看到其它事务之后的更改和尚未提交的更改。例外情况:查询语句能够看到同一个事务在查询语句执行前操作的数据。此特例导致下面一个异常情况:当你更新了表中的某些行,select语句就能够看到被更新数据的最新版本。如果其它session同时更新了同一个表,这时你可能看到表处于数据库未定义的状态。
如果事务的隔离级别是REPEATABLE READ (默认级别),同一个事务的所有的一致性读都是从第一次read时建立的快照版本中读取的。commit当前事务然后在查询就会得到更新一些的快照。
在READ COMMITTED隔离级别下,每一个一致性读都会刷新快照并从快照中读取数据。
一致性读是InnoDB在READ COMMITTED and REPEATABLE READ下默认的查询模式,一致性读不会对访问的表加任何锁,因此其它事务的修改表操作可以和查询动作并行执行。
假设你是在REPEATABLE READ下运行,当发生一致性读操作时(也就是说,执行了一条普通的查询语句),InnoDB引擎会给的事务一个”时间点”,该时间点决定了事务看到的数据范围。如果在该时间点之后另外一个事务删除了一行数据并提交了事务,你不会看到这行数据已经被删除。insert和update也是类似的处理方式。
注意
数据库对SELECT提供了快照查询功能,但是快照对DML语句并不一定有效。如果你在一个事务中insert或修改某些数据然后commit,另外一个事务的即使在REPEATABLE READ级别下执行update或delete语句也可能对刚才提交的数据产生影响,即使该事务在快照中查找不到这些数据。
一旦一个事务update 或 delete 了其它事务提交的数据,那么当前会看到这些数据,并且是最新版本。你可能遇到过下面的情况:``` sql 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语句或重新开启一个事务执行查询,以便更新“时间点”。
这就是多版本并发控制(multi-versioned concurrency control)。
下面的例子中,session A只有在session B提交了insert数据并且A也commit后,才能看到B插入的数据。这样session A的时间点才能在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 LOCK IN SHARE MODE;
在READ COMMITTED隔离级别下,每个一致性读事务都会刷新快照然后在从快照中读取。在LOCK IN SHARE下,一个读锁替代快照:一个查询语句将会被阻塞直到它获取最新的数据。
一致性读在一些DDL语句中不起效:
一致性读不会在DROP TABLE语句下工作,因为MySQL无法使用已经drop的表。
-
一致性读不会在ALTER TABLE语句下工作,因为这个语句会生成原始表的临时备份,一旦备份构建成功原始表将会被删除。如果此时用一致性读,会查不到数据。因为表中并不存在该事务时间点之前的数据。在这种情况下,会产生一个错误:ER_TABLE_DEF_CHANGED(table 的定义已经变更,请重试该事务)。
译者注:在MySQL5.7 RR下实验表明:只要开启了事务并在表中执行了语句(select,update or delete),只要没有commit,drop table,alter table就会被阻塞。
一些select语句,像INSERT INTO … SELECT, UPDATE … (SELECT), and CREATE TABLE … SELECT ,这些没有声明FOR UPDATE or LOCK IN SHARE MODE,使用一致性读的情况如下:
默认情况下,InnoDB会使用较粗粒度的锁,select部分与在READ COMMITTED模式下执行相同:即使是同一个事务,也会刷新快照。
如果打开了innodb_locks_unsafe_for_binlog选项在READ UNCOMMITTED,READ COMMITTED, or REPEATABLE READ (除了SERIALIZABLE以外的级别),在这种情况下,对查询的表不会加锁。