文章目录
- 🌈 一、事务的基本概念
- ⭐ 1. 事务是什么
- ⭐ 2. 事务的特性
- 🌈 二、事务的版本支持
- 🌈 三、事务的提交方式
- ⭐ 1. 查看事务的提交方式
- ⭐ 2. 设置事务的提交方式
- 🌈 四、事务的特性证明
- ⭐ 1. 事务的常规操作
- ⭐ 2. 证明事务的原子性
- ⭐ 3. 证明事务的持久性
- ⭐ 4. 手动开启的事务必须手动提交
- ⭐ 5. 单条 sql 与事务的关系
- 🌈 五、事务的隔离级别
- ⭐ 1. 隔离级别的分类
- ⭐ 2. 隔离级别的查看
- ⭐ 3. 隔离级别的设置
- ⭐ 4. 读未提交
- ⭐ 5. 读已提交
- ⭐ 6. 可重复读
- ⭐ 7. 可串行化
- ⭐ 8. 总结
- 🌈 六、事务一致性解析
- 🌈 七、多版本并发控制
- ⭐ 1. 数据库并发场景
- ⭐ 2. 并发控制的概念
- ⭐ 3. 记录的隐藏字段
- ⭐ 4. undo 日志
- ⭐ 5. 快照的基本概念
- ⭐ 6. 当前读和快照读
- ⭐ 7. Read View 读视图
- 🌈 八、RC 和 RR 的区别
🌈 一、事务的基本概念
⭐ 1. 事务是什么
-
事务是一组操作 (一条 / 多条 sql 语句) 的集合,是一个不可分割的工作单位。这些语句在逻辑上存在相关性,共同完成一个任务,事务主要用于处理操作量大,复杂度高的数据。
- 例:A 给 B 转账,需要将 A 的钱减少,B 的钱增多,这两条 sql 语句要同时执行才有意义,这两个操作要么只能成功或失败。
-
数据是要被多个用户共享的,在同一时刻可能会有多个请求访问数据库,MySQL 内部是采用多线程的方式来实现对数据的存储并发工作的,为了解决并发问题,事务就由此而生。
⭐ 2. 事务的特性
- 原子性:一个事务中的所有有操作,在处理的时候,只有全部完成或全部不完成两种可能行,不会结束在中间某个环节,事务在执行过程中如果发生错误,会被回滚到事务开始前的状态。
- 隔离性:数据库允许多个并发事务同时对数据进行读写和修改,事务会将要访问的数据进行隔离,防止在并发执行时由于交叉执行而导致的数据不一致。
- 永久性:事务处理结束后,对数据的修改是永久的,即使系统故障也不会丢失。
- 一致性:在事务开始前和结束后,数据库的完整性没有被破坏,即从一种状态变成另一种状态,它的结果是可预期的,能完成预定工作。
- 前面三种特性研究透彻了,一致性自然就出来了。
🌈 二、事务的版本支持
- 在 MySQL 中,只有使用了
InnoDB
存储引擎的数据库或表才支持事务,而MyISAM
则不支持。
查看数据库引擎
- 在 MySQL 中,可使用下面的指令查看数据库的存储引擎。
show engines; # 表格显示
show engines \G # 行显示
参数说明
- Engine:存储引擎的名称。
- Support:服务器对存储引擎的支持级别,YES 表示支持,NO 则表示不支持,DEFAULT 表示数据库默认使用的存储引擎,DISABLES 表示支持引擎但将其禁用了。
- Comment:对该存储引擎的描述。
- Transactions:该存储引擎是否支持事务。
- XA:表示存储引擎是否支持 XA 事务。
- Savepoints:表示存储引擎是否支持设置存档点。
🌈 三、事务的提交方式
- 事务的提交分为手动提交和自动提交。
⭐ 1. 查看事务的提交方式
- 通过如下指令可以知道事务的自动提交是否被打开。
- autocommit 的值为 ON 表示自动提交被打开,值为 OFF 则表示自动提交被关闭,需要手动提交。
show variables like 'autocommit';
⭐ 2. 设置事务的提交方式
set autocommit = 1; # 自动提交
set autocommit = 0; # 手动提交
- 关闭自动提交,即设置成手动提交
- 打开自动提交
🌈 四、事务的特性证明
准备工作
- 设置隔离级别:为了便于演示,现在将 MySQL 的隔离级别设置的低一点,设为读未提交。
set global transaction isolation level READ UNCOMMITTED;
-
重启终端:设置完隔离级别之后,当前会话的隔离级别并不会被改变,需要使用
quit
命令退出客户端之后,再重新启动。 -
查看隔离级别:重启完毕之后,再继续查看 MySQL 的隔离级别。
select @@transaction_isolation;
- 创建测试表:创建一张名为 account 的银行用户表,表中包含用户的主键 id、姓名 name 和账户余额 blance 这三个字段。
create table if not exists account
(id int primary key,name varchar(50) not null default '',blance decimal(10, 2) not null default 0.0
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
⭐ 1. 事务的常规操作
事务操作指令 | 说明 |
---|---|
begin; 或 start transaction; | 启动一个事务,该指令开始往后的所有 sql 都属于同一个事务,直到遇见 commit 为止 |
savepoint 保存点名称; | 在事务中创建指定名称的保存点 |
rollback to 保存点名称; | 将事务回滚到指定保存点 |
rollback; | 让事务回滚到最开始 |
commit; | 提交事务,事务在提交之后就无法回滚 |
- 启动两个客户端,左边的客户端负责启动事务,右边的客户端负责查看银行用户表中的信息。
- 让左客户端中的事务向 account 表中插入一条记录,左客户端中的事务在使用 commit 提交之前,有客户端也能查看该事务向表中插入的数据。
- 对左客户端的事务设置一个名为 save1 的保存点,然后再继续插入一条新的记录。
- 让左客户端的事务使用 rollback to 指定保存点 回滚到 save1 保存点处,右客户端此时已经无法看到表的第二条记录了。
- 左客户端直接使用 rollback 让事务回滚到最开始,有客户端再查看表数据,可以发现表中的数据已经全部都没了。
⭐ 2. 证明事务的原子性
- 启动两个客户端,左边的客户端负责启动事务,右边的客户端负责查看银行用户表中的信息。
- 让左客户端中的事务向 account 表中插入一条记录,左客户端中的事务在使用 commit 提交之前,有客户端也能查看该事务向表中插入的数据。
- 让左客户端在未 commit 时崩溃,MySQL 会自动回滚,这样就能证明事务的原子性。
⭐ 3. 证明事务的持久性
- 开启事务,并让事务往 account 表中插入一条数据。
- 左客户端使用 commit 提交事务,然后退出客户端,再通过右客户端查看表中数据。
- 表中的数据依然存在,说明事务只要提交了,就无法回滚,证实了事务的持久性。
⭐ 4. 手动开启的事务必须手动提交
- 只要输入 begin 或者 start transaction 手动开启了事务,便必须要通过 commit 提交,才会持久化,与是否设置 set autocommit 无关。
- 使用 begin 开启事务,并往表中插入记录。
- 在不 commit 的前提下将左客户端关闭, 会发现事务自动回滚了,说明并没有触发默认的自动提交。
⭐ 5. 单条 sql 与事务的关系
- 打开还是关闭 autocommit 自动提交影响的只是单条 sql 语句,在
InnoDB
中的每一条 sql 本质上都会被封装成一个事务。 - 如果自动提交被打开,则单条 sql 语句执行后会自动被提交。如果自动提交被关闭,则 sql 语句在执行后需要手动进行提交。
1. 打开自动提交时,会自动提交单条 sql
- 将自动提交 autocommit 设置为 on。
- 左客户端在不启动事务的情况下往表中插入数据。
- 不进行 commit 提交,直接将左客户端 quit 退出,此时再用过右客户端查看表中数据,发现并没有被回滚。
2. 关闭自动提交时,不自动提交单条 sql
- 将自动提交 autocommit 设置为 off。
- 在左客户端不开启事务,然后往表中插入一条数据。
- 不使用 commit,而是使用 quit 退出左客户端,再通过右客户端查看表中数据,此时发现刚刚插入的最新记录已经消失了。
🌈 五、事务的隔离级别
- MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务的方式进行。
- 一个事务可能由多条 SQL 语句构成,也就意味着任何一个事务,都有执行前、执行中、执行后三个阶段,而所谓的原子性就是让用户层要么看到执行前,要么看到执行后,执行中如果出现问题,可以随时进行回滚,所以单个事务对用户表现出来的特性就是原子性。
- 但毕竟每个事务都有一个执行的过程,在多个事务各自执行自己的多条 SQL 时,仍然可能会出现互相影响的情况,比如多个事务同时访问同一张表,甚至是表中的同一条记录。
- 数据库为了保证事务执行过程中尽量不受干扰,于是出现了隔离性的概念,而数据库为了允许事务在执行过程中受到不同程度的干扰,于是出现了隔离级别的概念。
⭐ 1. 隔离级别的分类
- 读未提交 (Read Uncommitted ):事务的最低隔离级别,该隔离级别下,所有的事务都可以看到其他事务没有提交的执行结果,实际生产中不可能使用这种隔离级别,因为这种隔离级别相当于没有任何隔离性,会存在很多并发问题,如脏读、幻读、不可重复读等。
- 读提交 (Read Committed):该隔离级别是大多数数据库的默认隔离级别,但它不是MySQL默认的隔离级别,它满足了隔离的简单定义,即一个事务只能看到其他已经提交的事务所做的改变,但这种隔离级别存在不可重复读和幻读的问题。
- 可重复读 (Repeatable Read):MySQL 默认的隔离级别,该隔离级别确保同一个事务在执行过程中,多次读取操作数据时会看到同样的数据,即解决了不可重复读的问题,但这种隔离级别下仍然存在幻读的问题。
- 串行化 (Serializable):事务的最高隔离级别,将所有到来的 CURD 操作按照先后顺序挨个执行,从而解决了幻读问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争问题,这种隔离级别太极端,实际生成中基本不使用。
⭐ 2. 隔离级别的查看
- 查看全局隔离级别:
select @@global.transaction_isolation;
- 查看会话隔离级别:登录时会默认拷贝一份全局隔离级别给会话的隔离界别。
select @@session.transaction_isolation;
select @@transaction_isolation;
⭐ 3. 隔离级别的设置
- 设置全局隔离级别:会影响后续的所有客户端,必须将设置该隔离界别的会话重启才会生效。
set global transaction isolation level {read uncommitted / read committed / repeatable read / serializable};
- 设置会话隔离级别:只会影响当前会话的隔离级别,不需要重启会话即可生效。
set session transaction isolation level {read uncommitted / read committed / repeatable read / serializable};
⭐ 4. 读未提交
- 先说结论:事务的最低隔离级别,效率高,但由于几乎没有加锁的原因,会产生很多并发问题,不建议使用。
- 一个事务的操作即使还没有提交,其他事务也能看到其所做的操作的结果。
- 该隔离级别下,会产生脏读的问题,即一个事务读取到另一个事务还没有提交(commit)的数据。
演示读未提交
- 启动两个客户端,将隔离级别都设置为读未提交,并查看此时 account 表中的数据。
- 在左右两个客户端各自使用 begin 开启一个事务,然后让左客户端往表中插入数据但不提交,让右客户端读取表中的数据。
- 可以发现,在未 commit 的情况下,其他客户端居然已经能够读取到数据了,这种情况被称之为脏读。
⭐ 5. 读已提交
- 只有当一个事务将他的所有操作提交之后,其他事务才能够看到这个事务所做的操作。
- 该隔离级别下会产生不可重复读的问题,即一个事务先后读取同一条记录,但两次读取的数据不同。
举个栗子
- 启动两个客户端,并将各自的隔离级别都设置成读提交。
- 左右两个客户端各自开启一个事务,左客户端的事务在未提交前,右客户端无法查看左客户端所做的操作。
- 当左客户端使用 commit 将事务提交之后,右客户端才能看到其对数据的操作。
⭐ 6. 可重复读
- 即使事务 A 使用 commit 提交事务了,事务 B 也无法查看到 A 的操作,只有重启一个新的事务才能看到。
- 在一个事务的执行过程中,多次相同的 select 语句查询到的结果相同。
- 一般的数据库在该隔离级别下在 insert 记录时会产生的幻读的问题,但 MySQL 不会。
- 幻读:一个事务按照条件查询数据时,没有对应的数据记录,但在插入数据时,又发现这条记录已经存在,就像出现了幻影。
举个栗子
- 启动两个客户端,并将各自的隔离级别设置成可重复读。
- 两个客户端各自新启一个事务,让左客户端的事务负责插入数据,右客户端的数据负责查询数据。
- 左客户端将事务提交,右客户端继续查询,发现查询到的结果并没有变化。
- 右客户端将自己的事务 commit 提交之后,才能查询到左客户端对表插入的新数据。
⭐ 7. 可串行化
- 最高隔离级别,会将到来的所有事务按照到达的先后顺序挨个执行,极大的影响了效率,不推荐使用该隔离级别。
举个栗子
- 启动两个客户端会话,并将各自的会话隔离级别设置成串行化 serializable。
- 让右客户端先启动事务,左客户端后启动,这样就让右事务比左事务先到达,如果这两个事务都进行读操作,则这两个事务可以并发执行,不会阻塞住。
- 在这两个事务都没有 commit 的情况下,如果哪个事务要对表执行插入操作,立马会被阻塞住。
- 只有当其他事务 (此处是右客户端的事务) 全部 commit 之后,插入操作被阻塞住的事务才会继续往下执行。
⭐ 8. 总结
- 事务的隔离级别越高,其安全性也就越高,数据库的并发性能也就越低。
- 不可重复读的重点在于修改和删除,同样的条件已经读取过的数据,等下次再读可能就不一样了。
- 幻读的重点在于新增,同样的条件,第一次和第二次读取的表的行数会不一样。
- MySQL 的默认隔离级别是可重复读,一般情况下不建议修改。
隔离级别与其会产生的问题
- 其中,√ 表示在对应隔离界别下会发生相应问题,× 则表示不会发生相应问题。
隔离级别 | 脏读 | 不可重复读 | 幻读 | 是否加锁读 |
---|---|---|---|---|
读未提交 | √ | √ | √ | 否 |
读已提交 | × | √ | √ | 否 |
可重复读 | × | × | × | 否 |
可串行化 | × | × | × | 是 |
🌈 六、事务一致性解析
- 在实现了原子性、隔离性、永久性后一致性自然就出来了。
- 事务执行的结果,必须使数据库从一个一致性状态变成另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库就处于一致性状态。
- 如果事务在执行过程中发生了错误,则会自动回滚到事务的最开始,即一致性需要原子性来保证。
- 事务处理结束后,对数据的修改必须是永久的,即便系统故障也不能丢失,即一致性需要持久性来保证。
- 多个事务同时访问同一份数据时,需要保证这多个事务在并发执行时,不会因为由于交叉执行而导致数据的不一致,即一致性需要隔离性来保证。
- 一致性还与用户的业务逻辑强相关,一般由 MySQL 提供技术支持,但如果用户本身的业务逻辑不行,最终也会破坏数据库的一致性。
- 因此,想要实现一致性的效果,需要原子性、持久性、隔离性,以及上层用户编写出正确的业务逻辑。
🌈 七、多版本并发控制
- MySQL 在同一时刻可能存在大量事务,如果不对这些事务加以控制,在执行时就可能会出现并发问题。
⭐ 1. 数据库并发场景
- 读 - 读:不存在任何问题,也不需要并发控制。
- 读 - 写:会产生线程安全问题,可能会存在事务的隔离性问题,即可能遇到脏读、幻读、不可重复读。
- 写 - 写:会产生线程安全问题,可能回存在更新丢失问题。
⭐ 2. 并发控制的概念
- 多版本并发控制 MVCC 是一种用来解决读 - 写冲突的无锁并发控制。
- 其主要依赖于记录的隐藏字段、undo 日志和 Read View 来实现。
- MySQL 会为事务分配单项增长的事务 id,为每个修改保存一个版本,将版本与事务 id 关联起来,读操作只读该事务开始前的数据库快照。
- 每个事务都要有自己的事务 ID,可以根据事务 ID 的大小,来决定事务到来的先后顺序。
1. MVCC 能为数据库解决的问题
- 在并发读 - 写数据库时,能做到在读操作时不用把写操作给阻塞住,写操作也不会阻塞住读操作,提高了数据库的并发读写性能。
- 解决了脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失的问题。
2. 如何管理事务 (事务的具体化)
- MySQL 的服务端 (mysqld) 可能会面临处理多个事务的情况,事务也有自己的生命周期,mysqld 也要对多个事务进行管理,因此在 mysqld 中,事务一定是对应的一个或者一套结构体 / 类对象。
- 事务也要有自己的结构体,事务 ID 对应的就是每个事务的结构体对象。当到达一个事务时,MySQL 会先 new 出一个事务的对象,再给该对象原子性的申请一个事务 ID,最后用事务 ID 初始化这个事务的结构体对象。
- 将多个事务的结构体对象使用某种数据结构管理组织起来,组织好之后就成了对对应数据结构的增删查改。
⭐ 3. 记录的隐藏字段
- 数据库表中的每条记录 (每行数据) 都会有 3 个隐藏的字段。
- 数据库表中的每条记录还有一个删除 flag 隐藏字段,即记录被更新或删除并不代表真的删了,而是删除 flag 变了。
隐藏的字段名 | 大小 | 说明 |
---|---|---|
DB_TRX_ID | 6 字节 | 记录创建这条记录 / 最后一次修改该记录的事务 ID |
DB_ROLL_PTR | 7 字节 | 回滚指针,指向这条记录的上一个版本,即指向历史版本,这些数据一般在 undo log 中 |
DB_ROW_ID | 6 字节 | 隐含的自增 ID (隐藏主键),如果数据库表没有设置主键,InnoDB 会自动以该字段生成一个聚簇索引 |
举个栗子
- 创建一张名为 student 的学生表,表中包含姓名 name 和年龄 age 这两个字段。
- 当向表中插入一条记录后,该记录不仅包含 name 和 age 字段,还包含三个隐藏字段。
⭐ 4. undo 日志
- MySQL 将来是以服务进程的方式在内存中运行的。
- 之前的索引、事务、隔离性、日志等机制都是在内存中完成的,即在 MySQL 内部的相关缓冲区中保存相关数据,完成各种判断操作。
- 然后在合适的时候,将相关数据刷新到磁盘当中,undo lo 就是 MySQL 中的一段内存缓冲区,用来保存日志数据。
MySQL 中的三大日志
- 多版本并发控制 MVCC 的实现主要依赖这三个日志中的 undo log,记录的历史版本就存储在 undo log 对应的缓冲区中。
日志 | 说明 |
---|---|
redo log | 重做日志,用于在 MySQL 崩溃后进行数据恢复,保证数据的持久性 |
bin log | 逻辑日志,用于对主从数据备份时进行数据同步,保证数据的一致性 |
undo log | 回滚日志,用于对已经执行的操作进行回滚操作,保证事务的原子性 |
⭐ 5. 快照的基本概念
- undo log 中有一个基于链表记录的历史版本链,undo log 中的每一个历史版本就是一个个的快照。
1. 模拟多版本并发控制 MVCC 的场景
- 当前有一个事务 ID 为 10 的事务,对 student 表中的记录进行修改,将 name (张三) 改成 name(李四)。
- 由于要进行的是写操作,因此需要先给该条记录加行级锁。
- 修改前,先将该条记录拷贝到 undo log 中,此时的 undo log 中会多出一行副本数据。
- 再将原始记录中的张三的名字改成李四,并将该记录的 DB_TRX_ID 改为 10,让回滚指针 DB_ROLL_PTR 指向 undo log 中副本数据的地址,从而指向上一个历史版本。
- 当将 10 号事务提交后,释放锁,此时最新的记录就是学生姓名为李四的记录了。
- 当前又有一个事务 ID 为 11 的事务,对 student 表中的记录进行修改,将 age(28) 修改成 age(38)。
- 事务 11 也要进行修改,因此需要先给最新的这条记录加上行级锁。
- 修改前,先将该行记录拷贝到 undo log 中,此时的 undo log 又多了一行副本数据。对新的副本采用头插的方式才插入到 undo log 中。
- 将原始记录的 age 改为 38,并将该记录的 DB_TRX_ID 改为 11,让回滚指针 DB_ROLL_PTR 执行刚拷贝到 undo log 中的新副本不数据的地址,从而执行该记录的上一历史版本。
- 将 11 号事务提交,然后释放行级锁,此时最新的记录就是 age 为 38 的那条。
2. 何时清除 undo log 中的历史版本链
- undo log 只是一段缓冲区,记录的是事务在运行期间对数据的历史修改。
- 当事务 commit 提交后,undo log 曾经的历史版本就会被释放。
⭐ 6. 当前读和快照读
- 当前读:访问当前最新的记录被称之为当前读,增删改操作必须得是当前读。
- 快照读:访问版本链中的历史记录被称之为快照读,事务在 select 时当前读和快照都有可能进行。
如何实现读写的多版本并发控制
- 在执行写操作时,使用的是当前读,访问的是最新的数据版本。
- 在执行读操作时,使用的是快照读,访问的是历史的数据版本。
⭐ 7. Read View 读视图
-
可用 Read View 保证不同的事务,能够看到不同的内容,即隔离级别的实现。
-
Read View 就是事务在进行快照读的时候产生的读视图,在改事务执行快照读的那一刻,会生成数据库系统当前的一个快照,用来记录并维护系统当前活跃事务的 ID。
- 当一个事务被开启时,都会被分配一个 ID,这个 ID 是递增的,索引越新的事务,ID 值越大。
-
Read View 在 MySQL 的源码中它就是一个类,本质是用来做可见性判断的,当事务对某个记录执行快照读时,会对改记录创建与一个 Read View 读视图,根据这个读视图来判断当前事务能够看到该记录的是哪个版本的数据。
Read View 类的源码
class ReadView
{// 省略......
private:// 高水位事务 ID: 大于等于这个 ID 的事务均不可见trx_id_t m_low_limit_id;// 低水位事务 ID: 小于这个 ID 的事务均可见trx_id_t m_up_limit_id;// 创建该 Read View 的事务的 IDtrx_id_t m_creator_trx_id;// 创建视图时的活跃事务 ID 列表ids_t m_ids;// 配合 purge,标识该视图不需要小于 m_low_limit_no 的 UNDO LOG,// 如果其他视图也不需要,则可以删除小于 m_low_limit_no 的 UNDO LOGtrx_id_t m_low_limit_no;// 标记视图是否被关闭bool m_closed;// 省略......
};
Read View 类中部分成员的说明
m_ids // 一张用来维护 Read View 生成时刻系统正活跃的事务 ID 的列表
up_limit_id // 用以记录 m_ids 列表中事务 ID 最小的 ID
low_limit_id // 存储目前已经出现过的对最大事务 ID 值 + 1 后的值
m_creator_trx_id // 创建该 Read View 的事务的 ID
- 由于事务 ID 是递增的,因此根据 Read View 中的 m_up_limit_id 和 m_low_limit_id,可将事务 ID 分成三个部分:
- 事务 ID < m_up_limit_id 的事务:一定是生成读视图时已经提交的事务。
- 事务 ID >= m_low_limit_id 的事务:一定时生成读视图时还没开启的事务。
- m_up_limit_id < 事务 ID < m_low_limit_id 的事务: 在生成读视图时可能处于活跃 / 已提交状态的事务,此时需要判断事务 ID 是否存在于 m_ids 中来判断该事务是否已经提交。
🌈 八、RC 和 RR 的区别
1. 演示当前读和快照都在 RR 个隔离界别下的区别
- 启动两个会话客户端,并将各自的会话隔离级别都设置成可重复读 (RR)。
- 让两个客户端各自 begin 一个事务,在左事务操作前,先让右事务查看一下表中的 account 表中的数据。
- 让左客户端事务对表中的信息进行修改并提交,右客户端的事务是看不到修改后的数据的。
- 让右客户端的事务使用如下指令以加锁共享的方式执行当前读,能看到被修改后的数据。
select * from account lock in share mode;
- 但如果修改一下 SQL 的执行顺序,在两个客户端各自开启事务后,先让左客户端的事务对表中的信息进行修改并提交,再让右客户端中的事务进行查看,此时右客户端中的事务就能直接看到修改后的数据。
- 在右客户端执行当前读,能够发现刚才读到的数据确实已经是最新的了。
- 这两种情况唯一区别在于,右事务在左事务修改数据之前是否进行过快照读。
- 当前面没有进行读取的情况下,就不存在历史版本,只能进行当前读。
- 在可重复读的隔离级别下,只要保证事务对数据的第一次读取和最后一次读取能够读取到同样的数据即可。
2. RC 和 RR 的本质区别
- Read View 形成的时机不同,会影响事务的可见性。
- 在 RR 隔离级别下,事务第一次进行快照读时会创建一个 Read View,将当前系统中活跃的事务记录下来,此后再进行快照读时就会直接使用这个 Read View 进行可见性判断,因此当前事务看不到第一次快照读之后其他事务所作的修改。
- 而在 RC 隔离级别下,事务每次进行快照读时都会创建一个 Read View,然后根据这个 Read View 进行可见性判断,因此每次快照读时都能读取到被提交了的最新的数据。
- RR 级别下快照读只会创建一次 Read View,所以 RR 级别是可重复读的,而RC 级别下每次快照读都会创建新的 Read View,所以 RC 级别是不可重复读的。
前读,能够发现刚才读到的数据确实已经是最新的了。