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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 一致读(consistent read),在《MySQL技术内幕 第二版》中称为一致性非锁定读(consistent nonlocking read),是指InnoDB使用多版本控制(multi versioning)向查询提供数据库在某个时间点的快照。

系列文章:

【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)。这也是我们在前面数据库事务模型文章中,对可重复读 隔离级别的一个实例证明。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
存储 自然语言处理 数据库
使用Elasticsearch映射定义索引结构
使用Elasticsearch映射定义索引结构
221 0
|
JavaScript
Javaweb之Vue组件库Element之Dialog对话框的详细解析
4.3.3 Dialog对话框 4.3.3.1 组件演示 Dialog: 在保留当前页面状态的情况下,告知用户并承载相关操作。其企业开发应用场景示例如下图所示
246 0
|
网络协议 小程序 对象存储
阿里云加了防盗链,小程序白名单如何填写
一、场景 场景一:我的阿里云服务器,做了图片连接的防盗链,需要添加域名白名单图片才可显示出来 场景二:我们的使用场景是这样的,DNS解析至阿里云的CND,CDN的回源指向阿里云的OSS
1308 1
|
编译器 C++
深拷贝和浅拷贝介绍
这篇文章讨论了C++中的数据拷贝,特别是浅拷贝和深拷贝的概念。对于基本类型和简单对象,拷贝是按位复制,即浅拷贝,类似于`memcpy()`函数的效果。当类包含动态分配的内存或其他资源时,需要显式定义拷贝构造函数以实现深拷贝,确保对象间的独立性。文中通过一个自定义的变长数组类`Array`示例说明了深拷贝的必要性,并展示了不使用深拷贝可能导致的问题。通常,如果类有指针成员,大部分情况需要深拷贝;否则,浅拷贝可能就足够了。文章还提到了在创建对象时需要预处理的情况,如记录对象创建时间或计数,这也需要深拷贝。
355 0
|
监控 数据挖掘 数据安全/隐私保护
ERP系统中的税务管理与优化
【7月更文挑战第25天】 ERP系统中的税务管理与优化
993 2
|
Shell Linux
Linux环境变量之shell中export定义全局变量和echo 变量的区别
Linux环境变量之shell中export定义全局变量和echo 变量的区别
|
前端开发 C++
【Qt】实现显示指定路径目录结构
使用 Qt 的 QTreeView 显示指定路径下的目录结构
838 0
|
关系型数据库 MySQL 数据库连接
[已解决]踩过的坑之mysql连接报“Communications link failure”错误
[已解决]踩过的坑之mysql连接报“Communications link failure”错误
|
XML JSON 缓存
Java实现根据商品ID获取1688商品类目数据API方法
Java实现根据商品ID获取1688商品类目数据API方法
|
存储 弹性计算 运维
实战指南 | Serverless 架构下的应用开发
基于 Serverless 架构的应用开发流程将会比基于传统架构的应用开发更简单。在 Serverless 架构下进行应用开发,用户通常只需要按照规范编写代码、构建产物,然后部署到线上即可。
实战指南 | Serverless 架构下的应用开发

热门文章

最新文章