[MySQL]MySQL事务
文章目录
- [MySQL]MySQL事务
- 1. 事务的概念
- 2. 为什么会出现事务
- 3. 事务的版本支持
- 4. 事务的提交方式
- 5. 事务常见操作方式
- 6. 事务隔离级别
- 事务隔离级别概念
- 查看事务的隔离级别
- 设置事务的隔离级别
- 读未提交(Read Uncommitted)
- 读提交(Read Committed)
- 可重复读(Repeatable Read)
- 可串行化(Serializable)
- 隔离级别总结
- 7. 关于一致性
- 8. 多版本并发控制( MVCC )
- 数据库并发场景
- 多版本并发控制的概念
- 多版本并发控制预备知识
- 模拟MVCC
- READ VIEW
- RR 与 RC的本质区别
1. 事务的概念
事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。
- 在应用层我们要实现一个操作可能需要在MySQL中执行多条SQL语句完成,在应用层看执行这个操作就是事务,而在MySQL中将事务看待成多条不可分开执行的SQL语句集合。
- 事务是为了实现应用层的某一个操作,因此事务中的SQL语句之间存在逻辑相关性。
- 事务主要用于处理操作量大,复杂度高的数据。
- 一个 MySQL 数据库,可不止一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向 MySQL 服务器发起事务处理请求,而每条事务至少一条 SQL ,存在很多事务都访问同样的表数据的情况。
- 事务由多条 SQL 构成,存在执行到一半出错或者不想再执行的情况。
由于事务存在多个事务都访问同样的表数据、执行到一半出错或者不想再执行的情况,因此,一个完整的事务,如果只是简单的SQL语句集合,就会出现很多问题,因此事务还需要满足如下四个属性:
- 原子性: 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
- 一致性: 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,事务执行前后,数据库中只包含成功事务提交的结果。
- 隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
上面四个属性,可以简称为 ACID :
- 原子性(Atomicity,或称不可分割性)
- 一致性(Consistency)
- 隔离性(Isolation,又称独立性)
- 持久性(Durability)
总结:
满足ACID属性的多条并在逻辑上存在相关性的SQL语句组成的集合称为事务。
2. 为什么会出现事务
事务不是伴随着数据库系统天生就有的,事务被设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型, 不需要我们去考虑各种各样的潜在错误和并发问题,因此事务本质上是为了应用层服务的。
- 有了事务,应用层使用数据库时只需要将要执行的操作交给数据库,事务控制并发操作等的工作就交给了数据库。
- 应用层使用数据库的事务进行操作只需要关心事务是否执行成功。
备注:我们后面把 MySQL 中的一行信息,称为一行记录。
3. 事务的版本支持
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
可以查看数据库引擎来获取是否支持事务的信息:
- Engine: 存储引擎名。
- Support: 服务器对存储引擎的支持级别。其中YES表示引擎受支持且处于活动状态、DEFAULT表示就像一样YES,但这是默认引擎、NO表示不支持引擎、DISABLED表示支持引擎,但已将其禁用。
- Comment: 存储引擎描述。其中InnoDB的描述是支持事务、行级锁、外键。
- Transactions: 是否支持事务。
- XA: 存储引擎是否支持XA事务。
- Savepoints: 是否支持事务保存点。
4. 事务的提交方式
事务的提交方式常见的有两种:
- 自动提交
- 手动提交
查看事务自动提交是否打开:
show variables like 'autocommit';
- 结果为ON表示自动提交已打开。
- 结果为OFF表示自动提交已关闭。
事务自动提交的打开与关闭:
SET AUTOCOMMIT=0; #禁止自动提交
SET AUTOCOMMIT=1; #开启自动提交
5. 事务常见操作方式
测试准备:
为了更好地对事务进行实验,做了以下准备:
- 将mysql的默认隔离级别设置成读未提交:
设置完之后需要重启客户端才能生效。
- 查看事务隔离级别:
- 创建测试表:
- 启动两个MySQL客户端:
事务正常操作测试1:
- 在两个客户端内输入start transaction/begin开启事务:
- 一个客户端设置保存点然后插入数据,另一个客户端查看表中数据:
- 重复前一步操作
- 重复前一步操作
- 插入数据的客户端回滚到s3保存点,另一个客户端查看数据:
由于s3保存点设置在插入第三条记录之前,因此回滚到s3保存点第三条记录会消失。
- 插入数据的客户端回滚到s1保存点,另一个客户端查看数据:
由于s1保存点设置在插入第一条记录之前,因此回滚到s1保存点之前插入的所有记录都会消失。
- 输入commit提交事务,查看表中数据:
事务正常操作测试2:
- 在两个客户端内输入start transaction/begin开启事务:
- 一个客户端然后插入数据,另一个客户端查看表中数据:
- 对插入数据的事务回滚:
由于没有保存点,会直接回滚到事务开始,因此插入的数据都会消失。
- 输入commit提交事务,查看表中数据:
事务正常操作测试3:
- 在两个客户端内输入start transaction/begin开启事务:
- 一个客户端然后插入数据,另一个客户端查看表中数据:
- 输入commit提交事务,查看表中数据:
- 对插入数据的事务回滚,查看表中数据:
事务已经提交了,回滚操作就无法生效了。
事务异常操作测试1:
- 查看表中数据和事务自动提交是否打开:
- 在两个客户端内输入start transaction/begin开启事务:
- 在一端插入数据,另一端查看数据:
- 输入crtl+\使插入数据端崩溃,然后在另一端查看数据:
事务未提交,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交),因此插入的数据消失了。
事务异常操作测试2:
- 查看表中数据和事务自动提交是否打开:
- 在两个客户端内输入start transaction/begin开启事务:
- 在一端插入数据,另一端查看数据:
- 关闭插入数据端,然后在另一端查看数据:
事务未提交,客户端关闭,MySQL自动会回滚(隔离级别设置为读未提交),因此插入的数据消失了。
事务异常操作测试3:
- 查看表中数据和事务自动提交是否打开:
- 在两个客户端内输入start transaction/begin开启事务:
- 在一端插入数据,另一端查看数据:
- 关闭插入数据端崩溃,然后在另一端查看数据:
事务提交了,客户端崩溃,MySQL数据不会在受影响,已经持久化。
说明:
- 手动输入start transaction/begin打开事务,就要手动提交。(关闭自动提交以上的测试结果是相同的)
- 事务运行结果一旦提交,操作结果就变成持久性的了。
事务异常操作测试4:
- 关闭事务自动提交:
- 一端不手动开始事务,一段手动开始事务查看表中数据:
- 在不开启事务的端口删除数据,在开始事物的端口查看数据:
- 在删除数据端输入ctrl + \使其崩溃,然后在另一端查看数据:
- 将手动开启事务端提交,查看数据:
事务异常操作测试5:
- 打开事务自动提交:
- 一端不手动开始事务,一段手动开始事务查看表中数据:
- 在不开启事务的端口删除数据,在开始事物的端口查看数据:
- 在删除数据端输入ctrl + d(效果和使客户端崩溃相同)关闭客户端,然后在另一端查看数据:
- 将手动开启事务端提交,查看数据:
说明:
- 没有手动输入start transaction/begin打开事务直接输入的单个SQL语句都被数据库封装成了事务。
- 没有手动输入start transaction/begin打开事务,无论是否遇到异常,都会自动提交(自动提交打开)。
总结:
- 手动打开事务或者关闭自动提交,必须手动提交,才会持久化。
- 事务一旦提交数据的操作就会持久化。
- 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC )
- 没有手动输入start transaction/begin打开事务,无论是否遇到异常,都会自动提交(自动提交打开)。
- 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚 。
- 从上面的例子,我们能看到事务本身的原子性–事务没有提交会自动回滚,持久性–事务一旦提交数据的操作就不变了。
事务操作注意事项:
- 手动开始事务可以使 start transaction 或者 begin,手动提交事务使用commit。
- 如果没有设置保存点并且事务还没有提交,也可以回滚,但是只能回滚到事务的开始。
- 如果一个事务被提交了,则不可以回滚 。
- 可以选择回滚到哪个保存点。
- InnoDB 存储引擎支持事务, MyISAM 存储引擎不支持事务 。
6. 事务隔离级别
事务隔离级别概念
MySQL可以能被多个客户端访问,多个客户端可能访问相同的数据,这些操作都由事务完成,一个事务可能又包含多个SQL语句,事务的并发执行可能会出现问题,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性。 数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别。
- 隔离性: 运行中的事务,进行相互隔离,使得各个事务运行中,被干扰的问题得到不同程度的解决。
- 隔离级别: 运行中的事务,受到其他事务干扰的程度级别。
隔离级别:
- 读未提交【Read Uncommitted】:所有的事务都可以看到其他事务没有提交的执行结果,但是相当于没有任何隔离性,也会有很多并发问题。
- 读提交【Read Committed】 :事务只能看到其他的已经提交的事务所做的改变, 大多数数据库的默认的隔离级别。
- 可重复读【Repeatable Read】:同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行,MySQL 默认的隔离级别。
- 可串行化【Serializable】:强制事务排序,多个事务必须串行化执行,使事务之间不可能相互冲突。
查看事务的隔离级别
SELECT @@global.tx_isolation; -- 查看全局隔离级别
SELECT @@session.tx_isolation; -- 查看会话(当前)全局隔离级别
SELECT @@tx_isolation; -- 默认同上
- 全局隔离级别的修改会影响后续所有客户端,修改全局隔离级别需要重启客户端才能生效。
- 会话的隔离级别修改只会影响该会话。
- 会话的默认隔离级别和全局隔离级别相同。
- 启动一个MySQL客户端就相当于开始一个会话,会话的生命周期是从启动客户端到关闭客户端。
- 会话隔离级别和全局隔离级别不同,会话会使用当前会话隔离级别。
查看全局隔离级别:
查看会话隔离级别:
设置事务的隔离级别
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} -- 设置当前会话 or 全局隔离级别
设置当前会话的隔离级别测试:
- 设置当前会话的隔离级别为读提交:
- 查看当前会话的全局隔离级别和会话隔离级别:
- 使用在原会话修改隔离级别前开始的另一个会话查看当前会话的全局隔离级别和会话隔离级别:
由于原会话修改的当前会话的隔离级别,因此不影响新的会话的隔离级别。
设置全局隔离级别测试:
- 设置全局隔离级别为串行化:
- 查看当前会话的全局隔离级别和会话隔离级别:
- 使用在原会话修改隔离级别前开始的另一个会话查看当前会话的全局隔离级别和会话隔离级别:
- 重启一个在原会话修改隔离级别后开始的新会话查看当前会话的全局隔离级别和会话隔离级别:
设置全局隔离级别后重启的客户端会话隔离级别才会跟着改变。
读未提交(Read Uncommitted)
读未提交是所有的事务都可以看到其他事务没有提交的执行结果。
- 几乎没有加锁,虽然效率高,但是问题太多。
读未提交测试
- 启动两个会话查看会话隔离级别:
- 两个会话同时手动开启事务:
- 一个会话操作数据,另一个会话并发的查看数据:
rollback就相当于事务结束了。
- 查看数据的会话的事务提交后查看数据:
测试出现的现象: 一个事务在执行中,读到另一个执行中事务的数据操作但是未commit的数据,这种现象叫做脏读 (dirty read)。
读提交(Read Committed)
读提交是事务只能看到其他的已经提交的事务所做的改变。
读提交测试:
- 启动两个会话查看会话隔离级别:
- 两个会话同时手动开启事务:
- 一个会话操作数据,另一个会话并发的查看数据:
- 查看数据的会话的事务提交后查看数据:
测试出现的现象: 同一个事务内,同样的读取,在不同的时间段 (依旧还在事务操作中),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)。
可重复读(Repeatable Read)
可重复读是同一个事务,在执行 中,多次读取操作数据时,会看到同样的数据行。
可重复读测试:
- 启动两个会话查看会话隔离级别:
- 两个会话同时手动开启事务:
- 一个会话操作数据,另一个会话并发的查看数据:
- 查看数据的会话的事务提交后查看数据:
补充一下: insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,叫做幻读 (phantom read)。MySQL在可重复读情况不会出现幻读。
可串行化(Serializable)
串行化是强制事务排序,多个事务必须串行化执行,使事务之间不可能相互冲突。
- 串行化读的时候加共享锁,也就是其他事务可以并发读,但是不能写,该事务自身可以写。写的时候加排它锁,其他事务不能并发写也不能并发读。
可串行化测试:
- 启动两个会话查看会话隔离级别:
- 两个会话同时手动开启事务:
- 两个会话都进行数据查询:
- 一个会话进行写操作,另一个会话查询数据:
- 查询数据的会话提交事务:
隔离级别总结
对MySQL中的隔离级别总结如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交(read uncommitted) | √ | √ | √ | 不加锁 |
读已提交(read committed) | × | √ | √ | 不加锁 |
可重复读(repeatable read) | × | × | × | 不加锁 |
可串行化(serializable) | × | × | × | 加锁 |
√:会发生该问题
×:不会发生该问题
7. 关于一致性
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,事务执行前后,数据库中只包含成功事务提交的结果。
- 事务在执行过程中如果发生错误,则需要自动回滚到事务最开始的状态,就像这个事务从来没有执行过一样,即一致性需要原子性来保证。
- 事务处理结束后,对数据的修改必须是永久的,即便系统故障也不能丢失,即一致性需要持久性来保证。
- 多个事务同时访问同一份数据时,必须保证这多个事务在并发执行时,不会因为由于交叉执行而导致数据的不一致,即一致性需要隔离性来保证。
- 此外,一致性与用户的业务逻辑强相关,如果用户本身的业务逻辑有问题,最终也会让数据库处于一种不一致的状态。
也就是说,一致性实际是数据库最终要达到的效果,一致性不仅需要原子性、持久性和隔离性来保证,还需要上层用户编写出正确的业务逻辑。
8. 多版本并发控制( MVCC )
数据库并发场景
数据库并发的场景有三种:
读-读
:不存在任何问题,也不需要并发控制 。读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读。写-写
:有线程安全问题,可能会存在更新丢失问题。
多版本并发控制的概念
多版本并发控制( MVCC )是一种用来解决 读-写冲突 的无锁并发控制。
- 读写并发是数据库使用过程中最常见的情况。
多版本并发控制预备知识
关于事务:
- MySQL中的事务有自己的事务id,根据事务id的大小,可以判断事务开始的先后顺序,事务id越小,事务开始越早。
- mysqld面临处理多个事务的情况时,为了方便管理多个事务,会使用在内存中申请空间并使用结构体描述事务,然后组织起来。
关于表结构:
MySQL中每条记录中存在三个隐藏属性列字段:
DB_TRX_ID
:6 byte,记录创建这条记录/最后一次修改该记录的事务ID 。DB_ROLL_PTR
: 7 byte,回滚指针,指向这条记录的上一个版本。DB_ROW_ID
: 6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引。
**关于表结构补充: **实际还有一个删除flag隐藏字段, 记录被更新或删除,由于MySQL服务是在内存级的进程,因此删除表中只需修改记录的标志位,然后在和磁盘进行IO时使得数据库的数据保持一致。
给出如下表结构和表数据:
实际在MySQL中的表结构和数据:
name | aeg | DB_TRX_ID | DB_ROW_ID | DB_ROLL_PTR |
---|---|---|---|---|
张三 | 28 | 9(示例) | 1(示例) | null(示例) |
Undo日志:
Undo日志时MySQL 中的一段内存缓冲区,用来保存日志数据的。
- Undo日志记录的是历史版本数据。
- Undo日志中记录的历史版本数据不能被修改。
- Undo日志会自动判断数据记录是否会有事务使用,如果判断没有事务会使用,就会清除记录。
模拟MVCC
现在有一个事务10,对student表中记录进行修改 – 将name(张三)改成 name(李四):
- 事务10,因为要修改,所以要先给该记录加行锁。
- 对记录进行操作前,先将要操作的记录拷贝到Undo日志中,使得Undo日志增加了一条历史版本的数据。
- 在修改后的记录中的回滚指针属性字段中记录在Undo日志中拷贝的修改前的记录的地址。
- 事务10提交,释放锁。
说明:
-
**MySQL会根据Undo日志生成"反操作"的SQL语句,执行这些SQL语句就能实现用历史数据,覆盖当前数据形成的回滚操作。**对于"反操作"语句举个例,比如Undo日志中记录的是插入操作,反操作记录的是删除操作。
-
由于多个事务会使用很多条SQL语句,Undo日志中会因此记录许多历史版本的记录,对于一个个版本,可以称之为一个个的快照。
-
插入、更新、删除数据都会形成历史版本。
-
当前读:读取最新的记录,就是当前读,增删改,都叫做当前读。
-
快照读:读取历史版本的记录。
-
select有可能当前读,也可能是快照读。
给出如下结论:
MVCC实现隔离性、不加锁的读写并发的原理就是让事务访问不同历史版本的记录。
- 数据的操作是当前读需要加锁。
- select使用快照读历史版本不需要加锁,因此可以并发执行。
- select使用快照读还是当前,快照读读取那个历史版本读取决于隔离级别。
READ VIEW
Read View就是事务进行 快照读 操作的时候生产的 读视图。
- 事务首次进行快照读操作的时候,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。
- Read View在MySQL源码中就是一个类,本质是用来进行可见性判断的,当事务对某个记录执行快照读的时候,对该记录创建一个Read View,根据这个Read View来判断,当前事务能够看到该记录的哪个版本的数据。
ReadView简化后的源码如下:
class ReadView {
// 省略...
private:
/** 高水位:大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id;
/** 低水位:小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;
/** 创建该 Read View 的事务ID*/
trx_id_t m_creator_trx_id;
/** 创建视图时的活跃事务id列表*/
ids_t m_ids;
/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
trx_id_t m_low_limit_no;
/** 标记视图是否被关闭*/
bool m_closed;
// 省略...
};
部分成员变量说明:
- m_ids:一张列表,用来维护Read View生成时刻,系统正活跃的事务ID。
- up_limit_id:记录m_ids列表中事务ID最小的ID 。
- low_limit_id:ReadView生成时刻系统尚未分配的下一个事务ID。
- creator_trx_id:创建该ReadView的事务ID。
在Undo日志中历史版本的数据形成了版本链,版本链中存有操作数据的事务id,利用版本链和READ VIEW中记录的事务id信息来判断快照读能否读取。
由于事务ID是单向增长的,因此根据Read View中的m_up_limit_id和m_low_limit_id,可以将事务ID分为三个部分:
- 事务ID小于m_up_limit_id的事务,一定是生成Read View时已经提交的事务,因为m_up_limit_id是生成Read View时刻系统中活跃事务ID中的最小ID,因此事务ID比它小的事务在生成Read View时一定已经提交了。
- 事务ID大于等于m_low_limit_id的事务,一定是生成Read View时还没有启动的事务,因为m_low_limit_id是生成Read View时刻,系统尚未分配的下一个事务ID。
- 事务ID位于m_up_limit_id和m_low_limit_id之间的事务,在生成Read View时可能正处于活跃状态,也可能已经提交了,这时需要通过判断事务ID是否存在于m_ids中来判断该事务是否已经提交。
示意图如下:
根据READ VIEW判断满足以下条件是能看到情况:
- 版本链中DB_TRX_ID小于等于m_up_limit_id的,因为这些事务是在形成快照前就已经提交的或者是事务自身进行的操作。
- 事务ID大于等于m_up_limit_id的小于等于m_low_limit_id但是却不在活跃ID列表m_ids中的事务,因此这些事务在形成快照前和当前事务并发执行,但是在生成时已经提交了。
源码策略如下:
bool changes_visible(trx_id_t id, const table_name_t& name) const
MY_ATTRIBUTE((warn_unused_result))
{
ut_ad(id > 0);
//1、事务id小于m_up_limit_id(已提交)或事务id为创建该Read View的事务的id,则可见
if (id < m_up_limit_id || id == m_creator_trx_id) {
return(true);
}
check_trx_id_sanity(id, name);
//2、事务id大于等于m_low_limit_id(生成Read View时还没有启动的事务),则不可见
if (id >= m_low_limit_id) {
return(false);
}
//3、事务id位于m_up_limit_id和m_low_limit_id之间,并且活跃事务id列表为空(即不在活跃列表中),则可见
else if (m_ids.empty()) {
return(true);
}
const ids_t::value_type* p = m_ids.data();
//4、事务id位于m_up_limit_id和m_low_limit_id之间,如果在活跃事务id列表中则不可见,如果不在则可见
return (!std::binary_search(p, p + m_ids.size(), id));
}
说明一下: MySQL调用该函数时会将该版本的DB_TRX_ID传给参数id,判断当前事务能否看到这个版本。
READ VIEW示例
给出以下记录:
name | aeg | DB_TRX_ID | DB_ROW_ID | DB_ROLL_PTR |
---|---|---|---|---|
张三 | 28 | null | 1 | null |
给出如下事务操作(表格从上到下代表时间线):
事务1 [id=1] | 事务2 [id=2] | 事务3 [id=3] | 事务4 [id=4] |
---|---|---|---|
事务开始 | 事务开始 | 事务开始 | 事务开始 |
… | … | … | 修改且已提交 |
进行中 | 快照读 | 进行中 | |
… | … | … |
- 事务4:修改name(张三) 变成name(李四)
- 当 事务2 对某行数据执行了 快照读 ,数据库为该行数据生成一个 Read View 读视图:
事务2的 Read View字段:
m_ids; // 1,3 -- 生成快照时事务4已经提交了
up_limit_id; // 1
low_limit_id; // 4 + 1 = 5
creator_trx_id // 2
此时的版本链:
事务2进行快照读会循环调用changes_visible函数,将版本链中的DB_TRX_ID依次传入,判断能否读到该版本的数据,直到找到能读取的版本为止:
//事务2的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
creator_trx_id // 2
//事务4提交的记录对应的事务ID
DB_TRX_ID=4
//比较步骤
DB_TRX_ID(4)< up_limit_id(1) ? 不小于,下一步
DB_TRX_ID(4)>= low_limit_id(5) ? 不大于,下一步
m_ids.contains(DB_TRX_ID) ? 不包含,说明,事务4不在当前的活跃事务中,因此事务4的版本可读。
//结论
事务4的更改,应该看到。
所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本
RR 与 RC的本质区别
测试示例1:
- 启动两个会话,查看当前隔离级别是否为可重复读:
- 查看表结构和表中数据:
- 两个会话都手动开始事务进行快照读操作:
- 一个会话进行修改操作,另一个会话进行快照读操作:
- 进行修改操作的会话提交事务,另一个会话进行快照读操作:
- 在未提交事务的会话中进行当前读:
select * from user lock in share mode
,以加共享锁方式进行读取,对应的就是当前读。
测试示例2:
- 启动两个会话,查看当前隔离级别是否为可重复读:
- 查看表结构和表中数据:
- 两个会话都手动开始事务,一个会话进行修改操作:
- 进行修改操作的会话提交事务,另一个会话进行快照读和当前读操作:
在只进行查询操作的会话中,由于事务中多次读取的结果相同,因此不违背可重复读的隔离级别。
测试结论:
- 事务中快照读的结果是非常依赖该事务首次出现快照读的地方。
RR 与 RC的本质区别
- 在RR级别下的某个事务只有第一次快照读时生成Read View,此后该事务都使用的是这个Read View,因此事务看不到之后的修改,实现可重复读。
- 在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。