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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 一致读(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)。这也是我们在前面数据库事务模型文章中,对可重复读 隔离级别的一个实例证明。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
存储 缓存 关系型数据库
详细解析MySQL中的innodb和myisam
总之,InnoDB和MyISAM各有千秋,选择合适的存储引擎应基于对应用程序特性的深入理解,以及对性能、数据完整性和可扩展性的综合考量。随着技术发展,InnoDB因其全面的功能和日益优化的性能,逐渐成为更广泛场景下的首选。然而,在特定条件下,MyISAM依然保留其独特的价值。
129 0
|
SQL 安全 关系型数据库
【Mysql-InnoDB 系列】锁定读
锁定读,是相对于一致(非锁定)读来说的。 当我们在同一个事务(T1)中先读数据,然后执行插入或更新相关数据时,普通的SELECT语句并不能给予足够的保护。其他事务也可能更新或删除我们在T1事务中查询的相同行。InnoDB支持两种类型的锁定读,来提供额外的保护
206 1
|
6月前
|
存储 SQL 关系型数据库
mysql中MyISAM和InnoDB的区别是什么
mysql中MyISAM和InnoDB的区别是什么
54 0
|
6月前
|
存储 关系型数据库 MySQL
MySQL 中InnoDB与MyISAM的区别是什么?
MySQL 中InnoDB与MyISAM的区别是什么?
163 0
MySQL 中InnoDB与MyISAM的区别是什么?
|
存储 SQL 关系型数据库
MySQL的InnoDB和MyISAM区别
MySQL的InnoDB和MyISAM区别
53 0
|
SQL 存储 关系型数据库
MySQL InnoDB 加锁机制
MySQL InnoDB 加锁机制
245 0
|
存储 SQL 缓存
详解MySQL存储引擎Innodb
详解MySQL存储引擎Innodb
491 0
详解MySQL存储引擎Innodb
|
SQL 存储 算法
彻底掌握 MySQL InnoDB 的锁机制
彻底掌握 MySQL InnoDB 的锁机制
|
存储 关系型数据库 MySQL
Mysql事务以及InnoDB和MyISAM区别讲解
Mysql事务以及InnoDB和MyISAM区别讲解
274 0
Mysql事务以及InnoDB和MyISAM区别讲解
|
关系型数据库 MySQL
【MySQL】innodb行锁变表锁
【MySQL】innodb行锁变表锁
122 0
【MySQL】innodb行锁变表锁