写点什么

【Mysql-InnoDB 系列】关于一致读

发布于: 2021 年 01 月 13 日
【Mysql-InnoDB 系列】关于一致读

系列文章:

【Mysql-InnoDB 系列】InnoDB 架构

【Mysql-InnoDB 系列】锁

【Mysql-InnoDB 系列】事务模型


一 概念

一致读(consistent read),在《MySQL 技术内幕 第二版》中称为一致性非锁定读(consistent nonlocking read),是指 InnoDB 使用多版本控制(multi versioning)向查询提供数据库在某个时间点的快照。

二 详细阐述

一致读查询能够看到在该时间点之前提交的事务所做的更改,而不会看到稍后或未提交的事务所做的更改。此规则的例外情况是,查询可以看到同一事务中早期语句所做的更改。这个例外导致了以下异常:如果更新表中的某些行,SELECT 会看到更新行的最新版本,但也可能会看到任何行的旧版本。如果其他会话同时更新同一个表,则这个异常意味着你可能会看到该表处于数据库中从未存在过的状态。

如果事务隔离级别是可重复读(默认的隔离级别),同一个事务中的所有一致读读取的都是由事务中第一个一致读创建的快照。可以通过提交当前事务并在提交后发出新的查询,来为你的查询获取更新的快照。

读已提交 隔离级别下,一个事务中的每个一致读都会设置并读取它自己的新快照。

一致读是 InnoDB 在读已提交 和 可重复读 隔离级别下处理 SELECT 语句的默认模式。一致读不会为它访问的表上设置任和锁,因此,其他会话可以在对表执行一致读取的同时自由修改这些表。

假设你正运行在默认的可重复读隔离级别下。当您发出一致的 read(即普通的 SELECT 语句)时,InnoDB 会给事务一个时间点,根据这个时间点,您的查询可以看到数据库。如果另一个事务删除一行并在分配了时间点后提交,则不会将该行视为已删除。插入和更新的处理方式类似。

注:

数据库状态的快照应用于事务中的 SELECT 语句,而不一定应用于 DML 语句。如果插入或修改某些行,然后提交该事务,则从另一个并发可重复读取事务发出的 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 查询 或 启动一致性快照的事务。这被称为多版本并发控制。

在下面的示例中,会话 A 仅在 B 提交了 insert 并且 A 也提交了 insert 时才看到 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 | ---------------------
复制代码

如果你想要看到数据库的“最新”状态,可以使用读已提交隔离级别 或 锁定读:

SELECT * FROM t FOR SHARE;
复制代码

三 读已提交

读已提交 隔离级别下,事务中的每个一致读设置并读取它自己的新快照。通过 FOR SHARE,将会发生锁定读:SELECT 语句被阻塞,直到包含最新行的事务结束

一致读在特定的 DDL 语句下不会生效:

1、一致读不适用于 DROP TABLE 语句,因为 MySQL 无法使用一个已经 drop 掉的表,InnoDB 会销毁这张表。

2、一致读不适用于 ALTER TABLE 操作,ALTER TABLE 会生成原始表的一个临时副本,并在临时副本建立后删除原始表。在事务中重新发出一致读取时,新表中的行不可见,因为在获取事务快照时,这些行不存在。在这种情况下,事务会返回错误:ER_TABLE_DEF_CHANGED,“表定义发生变化,请重试事务”。

SELECT 语句读的类型各不相同,例如 INSERT INTO...SELECT, UPDATE...(SELECT),以及 CREATE TABLE ... SELECT,这些未指定 FOR UPDATE 或 FOR SHARE:

1、默认情况下,InnoDB 对这些语句使用更强的锁,SELECT 部分的作用类似于读已提交,其中每个一致读(即使在同一事务中)都设置并读取自己的新快照。

2、要在这种情况下执行非锁定读取,请将事务的隔离级别设置为 读未提交 或读已提交,以避免对从所选表读取的行设置锁。

四 读已提交隔离级别下的一致读

如前面所说,READ COMMITTED 事务隔离级别下,一致读总是读取行的最新版本,如果行被锁定,就读取该行版本的最新的快照。一个示例如下:

1、事务隔离级别设置/确认为读已提交

mysql> select @@tx_isolation;+----------------+| @@tx_isolation |+----------------+| READ-COMMITTED |+----------------+
复制代码

2、创建表,表 t 是一张示例表,建表语句:

CREATE TABLE `t` (  `i` int(11) NOT NULL,  PRIMARY KEY (`i`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
//插入1条数据insert into t values(1);
复制代码

3、开启会话 A,begin 开启事务:

mysql> begin;Query OK, 0 rows affected (0.01 sec)
mysql> select * from t where i=1;+---+| i |+---+| 1 |+---+1 row in set (0.00 sec)
mysql> select * from t where i=5;Empty set (0.01 sec)
复制代码

由于只有一条记录 1,所以查询 i=5 时返回为空。

4、打开新的会话 B,begin 开启事务,执行更新动作,但先不提交:

mysql> begin    -> ;Query OK, 0 rows affected (0.00 sec)
mysql> update t set i=5 where i=1;Query OK, 1 row affected (0.02 sec)Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t where i=1;Empty set (0.00 sec)
mysql> select * from t where i=5;+---+| i |+---+| 5 |+---+1 row in set (0.00 sec)
复制代码

5、回到会话 A,查询 i=5:

mysql> select * from t where i=5;
Empty set (0.01 sec)
复制代码

由于 B 事务并未提交,而当前隔离级别为读已提交,所以查不到 i=5 的记录是符合预期的、

6、会话 B 提交事务:

mysql> commit;Query OK, 0 rows affected (0.01 sec)
复制代码

7、在回到会话 A,查询 i=5 记录:

mysql> select * from t where i=5;+---+| i |+---+| 5 |+---+1 row in set (0.00 sec)
复制代码


由此可见,对于 READ COMMITTED 事务隔离级别,从数据库理论来看,违背了事务 ACID 中的隔离性(I)。这也是我们在前面数据库事务模型文章中,对可重复读 隔离级别的一个实例证明。

发布于: 2021 年 01 月 13 日阅读数: 33
用户头像

磨炼中成长,痛苦中前行 2017.10.22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
【Mysql-InnoDB 系列】关于一致读