innodb 一致性非锁定读取

Nona ·
更新时间:2024-11-13
· 810 次阅读

一致性非锁定读取 Consistent Nonlocking Reads

目录正文一、一致读取的规则二、触发一致读取的语句三、可重复读下的一致读取四、避免一致读取的弊端五、DDL、select子句对一致读取的适用性六、到底什么叫数据库多版本并发控制

正文开始前,先把概念讲明白:
一致性读取(consistent read):
一种读取操作,使用基于时间点的快照(snapshot )展示查询结果,而不管同时运行的其他事务执行的更改如何。 如果查询的数据已被另一个事务更改,则将基于撤消日志(undo log)的内容来重建原始数据。 运用这项技术,可以缓解由锁导致的事务间的等待而降低并发性能的问题

当事务隔离级别为可重复读(REPEATABLE READ)时,快照建立的时间点为该事务中的第一次读取操作的时间点。 读已提交(READ COMMITTED)时,在每次读取时会重复建立快照。
当事务隔离级别为可重复读读已提交时,innodb默认开启一致性读取。由于一致性读取不会在使用的表上加任何锁,其他的会话可以自由的修改这些被一致性读取使用的表

上方高亮文字原文: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.

由于在查询时无需等待其他事务持有的锁,增强了并发性。
多版本控制介绍


作者:慕宗悫之长风



一致性 innodb

需要 登录 后方可回复, 如果你还没有账号请 注册新账号