一致性非锁定读取 Consistent Nonlocking Reads
目录正文一、一致读取的规则二、触发一致读取的语句三、可重复读下的一致读取四、避免一致读取的弊端五、DDL、select子句对一致读取的适用性六、到底什么叫数据库多版本并发控制正文开始前,先把概念讲明白:
一致性读取(consistent read):
一种读取操作,使用基于时间点的快照(snapshot )展示查询结果,而不管同时运行的其他事务执行的更改如何。 如果查询的数据已被另一个事务更改,则将基于撤消日志(undo log)的内容来重建原始数据。 运用这项技术,可以缓解由锁导致的事务间的等待而降低并发性能的问题。
上方高亮文字原文:Because a consistent read does not set any locks on the tables it accesses, other sessions are free to modify those tables while a consistent read is being performed on the table.
快照(snapshot):
在特定时间点的“数据画像”(A representation of data),即使其他事务对这些数据做了更改,“画像”中的数据也不变。(可以理解为克隆了一个数据的副本,但原著中并没有用类似clone date之类的字眼)
撤销日志(undo log):
是一个保存活跃事务修改过后的旧数据的储存空间(新数据存在表里,旧数据存在该空间中)。如果另一个事务需要查看原始数据(作为一致读取操作的一部分),则将从该存储区域中检索修改前的数据。
在MySQL 5.6和MySQL 5.7中,可以使用innodb_undo_tablespaces变量将撤消日志存在撤消表(undo tablespaces)中,该撤消表可以放置在其他存储设备(例如SSD)上。 在MySQL 8.0中,撤消日志位于初始化MySQL时创建的两个默认撤消表中,并且可以使用CREATE UNDO TABLESPACE语法创建其他撤消表。
The undo log is split into separate portions, the insert undo buffer and the update undo buffer.
正文一致读取意味着InnoDB使用多版本(multi-versioning)在某个时间点向查询(query)呈现数据库的快照。
一、一致读取的规则 一个查询只能看到该时间点之前做的改动,该时间点之后的改动或者未提交的改动都看不到。这条规则有个例外:在同一个事物第一次查询建立快照后,再次查询时,能查到该事物在两次查询之间做的更改。这也意味着查询到的这个更改是其他事务查询不到的。
二、触发一致读取的语句select ...#SELECT 不使用FOR UPDATE或LOCK IN SHARE MODE
an ordinary SELECT statement : 常规的select语句
三、可重复读下的一致读取设想一下使用可重复读的事务做一致性读取时,其他事务做插入、更新、删除的操作都不能被查询到。
当一个事务插入或修改某些行之后,另一个可重复读的事务同时做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.更新影响到10行
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.再次查询,能查询到10行。
#参见上文中一致读取规则的例外(高亮文本)。
这说明,在一个事务中基于数据库的快照适用于SELECT语句而不适用于DML语句。
The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements.
通过提交当前事务再进行SELECT或 START TRANSACTION WITH CONSISTENT SNAPSHOT的操作,可以获取最新的数据库快照。
下面这个例子中,会话A、B同时开始,关闭自动提交后,在B提交的前后,A均不能查询到B提交的内容,只有在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) 使用锁定读取(LOCK IN SHARE MODE):SELECT * FROM t FOR SHARE;
在读已提交的事务隔离级别中,每一个一致性读取都在单独的事务中并拥有各自的最新快照;当使用 LOCK IN SHARE MODE 时,SELECT 会被阻塞直到包含最新行的事务结束。
五、DDL、select子句对一致读取的适用性一致性读取同样也不适用某些DDL语句:
一致的读取对 DROP TABLE 不起作用,因为MySQL无法使用已删除的表,并且InnoDB会破坏该表。 一致性读取对 ALTER TABLE 不起作用,因为该语句将创建原始表的临时副本,并在构建临时副本时删除原始表。 当在事务中重新发出一致读取时,新表中的行是不可见的,因为在获取事务的快照时这些行不存在。 在这种情况下,事务返回错误:ER_TABLE_DEF_CHANGED,“表定义已更改,请重试事务”。对于未指定 FOR UPDATE 或 LOCK IN SHARE MODE 的 INSERT INTO … SELECT,UPDATE …(SELECT)和 CREATE TABLE … SELECT 之类的select条件子句,读取的类型有所不同:
默认情况下,InnoDB使用更强的锁,而SELECT部分的行为类似于READ COMMITTED,其中每个一致读取(即使是在同一事务中)也会设置并读取自己的新快照。 在这种情况下若想使用一致读取且不加行锁,请启用innodb_locks_unsafe_for_binlog选项,并将事务的隔离级别设置为READ UNCOMMITTED,READ COMMITTED或REPEATABLE READ(即SERIALIZABLE以外的其他任何一种)。 在这种情况下,在所选定的表中读取的行上不会设置锁定。 六、到底什么叫数据库多版本并发控制到底什么叫多版本并发控制(multi-versioned concurrency control MVCC):
这种技术可以使具有特定隔离级别的InnoDB事务执行一致读取操作。 也就是说,当查询其他事务正在更新的行时,查询到的是这些行中更新之前的值。
to query rows that are being updated by other transactions, and see the values from before those updates occurred.
由于在查询时无需等待其他事务持有的锁,增强了并发性。
多版本控制介绍