【MySQL】事务管理

文章目录

  • 什么是事务
  • 为什么会出现事务
  • 事务的版本支持
  • 事务的提交方式
  • 事务的相关演示
  • 事务的隔离级别
    • 查看与设置隔离级别
    • 读未提交(Read Uncommitted)
    • 读提交(Read Committed)
    • 可重复读(Repeatable Read)
    • 串行化(Serializable)
  • 关于一致性
  • 多版本并发控制
    • 记录中的3个隐藏字段
    • undo日志
    • 快照
    • Read View
  • RR 与 RC 的本质区别


什么是事务

事务 就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据,比如转账就涉及多条SQL语句,包括查询余额(select)、在当前账户上减去指定金额(update)、在指定账户上加上对应金额(update)等,将这多条SQL语句打包便构成了一个事务。

MySQL同一时刻可能存在大量事务,如果不对这些事务加以控制,在执行时就可能会出现问题。比如单个事务内部的某些SQL语句执行失败,或是多个事务同时访问同一份数据导致数据不一致的问题。

因此一个完整的事务绝对不是简单的SQL集合,还需要满足如下四个 特性

  • 原子性: 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,则会自动回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 隔离性: 数据库允许多个事务同时访问同一份数据,隔离性可以保证多个事务在并发执行时 ,不会因为由于交叉执行而导致数据的不一致。
  • 一致性: 在事务开始之前和事务结束之后,数据库的完整性没有被破坏,这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库的可以自发性地完成预设的工作。

上面的四个属性简称ACID

  • 原子性(Atomicity,又称不可分割性)。
  • 一致性(Consistency)。
  • 隔离性(Isolation,又称独立性)。
  • 持久性(Durability)。

其中原子性、隔离性和持久性是手段,一致性是目的。只要原子性、隔离性和持久性能够保证,那么一致性一定能够保证。


为什么会出现事务

十五被MySQL编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。如果MySQL只是单纯的提供数据存储服务,那么用户在访问数据库时就需要自行考虑各种潜在问题,包括网络异常、服务器宕机等。因此事务本质是为了应用服务的,而不是伴随着数据库系统天生就有的。


事务的版本支持

MySQL中只有使用了 InnoDB 数据库引擎的数据库或表才支持事务,MyISAM 不支持。

在这里插入图片描述

  • Engine: 表示存储引擎的名称。
  • Support: 表示服务器对存储引擎的支持级别,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎,DISABLED表示支持引擎但已将其禁用。
  • Comment: 表示存储引擎的简要说明。
  • Transactions: 表示存储引擎是否支持事务,可以看到InnoDB存储引擎支持事务,而MyISAM存储引擎不支持事务。
  • XA: 表示存储引擎是否支持XA事务。
  • Savepoints: 表示存储引擎是否支持保存点。

事务的提交方式

💕 查看事务的提交方式

show variables like 'autocommit';

在这里插入图片描述

💕 设置事务的提交方式

set autocommit=0/1;

在这里插入图片描述


事务的相关演示

💕 查看隔离级别

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ | -- 可重复读
+-----------------+
1 row in set, 1 warning (0.00 sec)

💕 设置读未提交的隔离级别

mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

当我们设置完隔离级别后需要重新登录才能生效。

在这里插入图片描述

准备测试表

创建一个银行用户表,表中包含用户的id、姓名和账户余额。如下:

在这里插入图片描述

💕 事务的开始与回滚

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

说明一下:

  • 使用beginstart transaction命令,可以启动一个事务。
  • 使用savepoint 保存点命令,可以在事务中创建指定名称的保存点。
  • 使用rollback to 保存点命令,可以让事务回滚到指定保存点。
  • 使用rollback命令,可以直接让事务回滚到最开始。
  • 使用commit命令,可以提交事务,提交事务后就不能回滚了。

💕 原子性

在这里插入图片描述
如果左终端中的事务在提交之前因为某些原因与MySQL断开连接,那么MySQL会自动让事务回滚到最开始,这时右终端中就看不到之前插入的记录了。

💕 持久性

在这里插入图片描述

左终端中的事务在提交后与MySQL断开连接,这时右终端中仍然可以看到之前插入的记录,因为事务提交后数据就被持久化了。

💕 begin会自动更改提交方式

在这里插入图片描述

如果左终端中的事务在提交之前与MySQL断开连接,那么MySQL依旧会自动让事务回滚到最开始,这时右终端中就看不到之前新插入的记录了。也就是说,使用begin或start transaction命令启动的事务,都必须要使用commit命令手动提交,数据才会被持久化,与是否设置autocommit无关。

实际全局变量autocommit是否被设置影响的是单条SQL语句,InnoDB中的每一条SQL都会默认被封装成事务。autocommit 为ON,则单条SQL语句执行后会自动被提交,如果为OFF,则SQL语句执行后需要使用 commit 进行手动提交。 实际我们之前一直都在使用单SQL事务,只不过autocommit默认是打开的,因此单SQL事务执行后自动就被提交了。


事务的隔离级别

  • MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务的方式进行。
  • 一个事务可能由多条SQL语句构成,也就意味着任何一个事务,都有执行前、执行中和执行后三个阶段,而所谓的原子性就是让用户层要么看到执行前,要么看到执行后,执行中如果出现问题,可以随时进行回滚,所以单个事务对用户表现出来的特性就是原子性。
  • 但毕竟每个事务都有一个执行的过程,在多个事务各自执行自己的多条SQL时,仍然可能会出现互相影响的情况,比如多个事务同时访问同一张表,甚至是表中的同一条记录。
  • 数据库为了保证事务执行过程中尽量不受干扰,于是出现了隔离性的概念,而数据库为了允许事务在执行过程中受到不同程度的干扰,于是出现了隔离级别的概念。

数据库的隔离级别有以下四种:

  • 读未提交(Read Uncommitted): 在该隔离级别下,所有的事务都可以看到其他事务没有提交的执行结果,实际生产中不可能使用这种隔离级别,因为这种隔离级别相当于没有任何隔离性,会存在很多并发问题,如 脏读、幻读、不可重复读 等。
  • 读提交(Read Committed): 该隔离级别是大多数数据库的默认隔离级别,但它不是MySQL默认的隔离级别,它满足了隔离的简单定义:一个事务只能看到其他已经提交的事务所做的改变,但这种隔离级别存在 不可重复读幻读 的问题。
  • 可重复读(Repeatable Read): 这是MySQL默认的隔离级别,该隔离级别确保同一个事务在执行过程中,多次读取操作数据时会看到同样的数据,即解决了不可重复读的问题,但这种隔离级别下仍然存在 幻读 的问题。
  • 串行化(Serializable): 这是事务的最高隔离级别,该隔离级别通过强制事务排序,使之不可能相互冲突,从而解决了幻读问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争问题,这种隔离级别太极端,实际生成中基本不使用。

虽然数据库事务的隔离级别有以上四种,但一个稳态的数据库只会选择这其中的一种,作为自己的默认隔离级别。但数据库默认的隔离级别有时可能并不满足上层的业务需求,因此数据库提供了这四种隔离级别,可以让我们自行设置。隔离级别基本上都是通过加锁的方式实现的,不同的隔离级别对锁的使用是不同的,常见的有表锁、行锁、写锁、间隙锁(GAP)、Next-Key锁(GAP+行锁)等。


查看与设置隔离级别

查看全局隔离级别

select @@global.tx_isolation;

在这里插入图片描述

查看会话隔离级别

select @@session.tx_isolation;

在这里插入图片描述

此外,通过 select @@tx_isolation 命令,也可以查看当前会话的隔离级别。如下:

在这里插入图片描述

设置会话隔离级别

通过 set session transaction isolation level 隔离级别命令,可以设置当前会话的隔离级别。

在这里插入图片描述

说明一下: 设置会话的隔离级别只会影响当前会话,新起的会话依旧采用全局隔离级。

设置全局隔离级别

通过 set global transaction isolation level 隔离级别 命令,可以设置全局隔离级别。

在这里插入图片描述

设置全局隔离级别会影响后续的新会话,但当前会话的隔离级别没有发生变化,如果要让当前会话的隔离级别也改变,则需要重启会话。


读未提交(Read Uncommitted)

启动两个终端,将隔离级别设置为读未提交,并查看此时银行用户表中的数据。如下:

在这里插入图片描述

在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务就已经能够看到了。

在这里插入图片描述

说明一下:

  • 读未提交是事务的最低隔离级别,几乎没有加锁,虽然效率高,但是问题比较多,所以严重不建议使用。
  • 一个事务在执行过程中,读取到另一个执行中的事务所做的修改,但是该事务还没有进行提交,这种现象叫做脏读。

读提交(Read Committed)

启动两个终端,将隔离级别都设置为读提交,并查看此时银行用户表中的数据。

在这里插入图片描述

左边的事务所做的修改在没有提交之前,右边终端是无法看到的。

在这里插入图片描述

当左终端中的事务提交后,右终端中的事务才能看到修改后的数据。

在这里插入图片描述

一个事务在执行过程中,两个相同的select查询得到了不同的数据,这种现象叫做 不可重复读


可重复读(Repeatable Read)

启动两个终端,将隔离级别都设置为可重复读,并查看此时银行用户表中的数据。如下:

在这里插入图片描述

两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务无法看到。

在这里插入图片描述

并且当左终端中的事务提交后,右终端中的事务仍然看不到修改后的数据。

在这里插入图片描述

只有当右终端中的事务提交后再查看表中的数据,这时才能看到修改后的数据。

在这里插入图片描述

说明一下:

  • 在可重复读隔离级别下,一个事务在执行过程中,相同的select查询得到的是相同的数据,这就是所谓的可重复读。
  • 一般的数据库在可重复读隔离级别下,update数据是满足可重复读的,但insert数据会存在幻读问题,因为隔离性是通过对数据加锁完成的,而新插入的数据原本是不存在的,因此一般的加锁无法屏蔽这类问题。
  • 一个事务在执行过程中,相同的select查询得到了新的数据,如同出现了幻觉,这种现象叫做幻读。
  • MySQL是通过Next-Key锁(GAP+行锁)来解决幻读问题的。

串行化(Serializable)

启动两个终端,将隔离级别都设置为串行化,并查看此时银行用户表中的数据
在这里插入图片描述

在两个终端各自启动一个事务,如果这两个事务都对表进行的是读操作,那么这两个事务可以并发执行,不会被阻塞。

在这里插入图片描述

但如果这两个事务中有一个事务要对表进行写操作,那么这个事务就会立即被阻塞。

在这里插入图片描述

直到访问这张表的其他事务都提交后,这个被阻塞的事务才会被唤醒,然后才能对表进行修改操作。

在这里插入图片描述

串行化是事务的最高隔离级别,多个事务同时进行读操作时加的是共享锁,因此可以并发执行读操作,但一旦需要进行写操作,就会进行串行化,效率很低,几乎不会使用。


总结:

在这里插入图片描述

  • 隔离级别越严格,安全性越高,但数据库的并发性能也就越低,在选择隔离级别时往往需要在两者之间找一个平衡点。
  • 表中只写出了各种隔离级别下进行读操作时是否需要加锁,因为无论哪种隔离级别,只要需要进行写操作就一定需要加锁。

关于一致性

事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库就处于 一致性状态

  • 事务在执行过程中如果发生错误,则需要自动回滚到事务最开始的状态,就像这个事务从来没有执行过一样,即一致性需要原子性来保证。
  • 事务处理结束后,对数据的修改必须是永久的,即便系统故障也不能丢失,即一致性需要持久性来保证。
  • 多个事务同时访问同一份数据时,必须保证这多个事务在并发执行时,不会因为由于交叉执行而导致数据的不一致,即一致性需要隔离性来保证。
  • 此外,一致性与用户的业务逻辑强相关,如果用户本身的业务逻辑有问题,最终也会让数据库处于一种不一致的状态。

也就是说,一致性实际是数据库最终要达到的效果,一致性不仅需要原子性、持久性和隔离性来保证,还需要上层用户编写出正确的业务逻辑。


多版本并发控制

数据库并发的场景有三种:

  • 读-读 :不存在任何问题,也不需要并发控制
  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

说明一下:

  • 写-写并发场景下的第一类更新丢失又叫做回滚丢失,即一个事务的回滚把另一个已经提交的事务更新的数据覆盖了,第二类更新丢失又叫做覆盖丢失,即一个事务的提交把另一个已经提交的事务更新的数据覆盖了。
  • 读-读并发不需要进行并发控制,写-写并发实际也就是对数据进行加锁,这里最值得讨论的是读-写并发,读-写并发是数据库当中最高频的场景,在解决读-写并发时不仅需要考虑线程安全问题,还需要考虑并发的性能问题。

多版本并发控制

多版本并发控制(Multi-Version Concurrency Control,MVCC) 是一种用来解决读写冲突的无锁并发控制,主要依赖记录中的 3个隐藏字段、undo日志和Read View 实现。

  • 为事务分配单向增长的事务ID,为每个修改保存一个版本,将版本与事务ID相关联,读操作只读该事务开始前的数据库快照。
  • MVCC保证读写并发时,读操作不会阻塞写操作,写操作也不会阻塞读操作,提高了数据库并发读写的性能,同时还可以解决脏读、幻读和不可重复读等事务隔离性问题。

记录中的3个隐藏字段

数据库中的每条记录都会有如下3个隐藏字段:

  • DB_TRX_ID:6字节,创建或最近一次修改该记录的事务ID。
  • DB_ROW_ID:6字节,隐含的自增ID(隐藏主键)。
  • DB_ROLL_PTR:7字节,回滚指针,指向这条记录的上一个版本。

说明一下:

  1. 采用InnoDB存储引擎建立的每张表都会有一个主键,如果用户没有设置,InnoDB就会自动以DB_ROW_ID产生一个聚簇索引。
  2. 此外,数据库表中的每条记录还有一个删除flag隐藏字段,用于表示该条记录是否被删除,便于进行数据回滚。

示例

创建一个学生表,表中包含学生的姓名和年龄。如下:

在这里插入图片描述

当向表中插入一条记录后,该记录不仅包含name和age字段,还包含三个隐藏字段。如下:

在这里插入图片描述

  • 假设插入该记录的事务的事务ID为9,那么该记录的DB_TRX_ID字段填的就是9。
  • 因为这是插入的第一条记录,所以隐式主键DB_ROW_ID字段填的就是1。
  • 由于这条记录是新插入的,没有历史版本,所以回滚指针DB_ROLL_PTR的值设置为null。
  • MVCC重点需要的就是这三个隐藏字段,实际还有其他隐藏字段,只不过没有画出。

undo日志

MySQL的三大日志如下:

  1. redo log:重做日志,用于MySQL崩溃后进行数据恢复,保证数据的持久性。
  2. bin log:逻辑日志,用于主从数据备份时进行数据同步,保证数据的一致性。
  3. undo log:回滚日志,用于对已经执行的操作进行回滚,保证事务的原子性。

MySQL会为上述三大日志开辟对应的缓冲区,用于存储日志相关的信息,必要时会将缓冲区中的数据刷新到磁盘。

MVCC的实现主要依赖三大日志中的undo log,记录的历史版本就是存储在undo log对应的缓冲区中的。


快照

现在有一个事务ID为10的事务,要将刚才插入学生表中的记录的学生姓名改为“李四”:

  • 因为是要进行写操作,所以需要先给该记录加行锁。
  • 修改前,先将该行记录拷贝到undo log中,此时undo log中就有了一行副本数据。
  • 然后再将原始记录中的学生姓名改为“李四”,并将该记录的DB_TRX_ID改为10,回滚指针DB_ROLL_PTR设置成undo log中副本数据的地址,从而指向该记录的上一个版本。
  • 最后当事务10提交后释放锁,这时最新的记录就是学生姓名为“李四”的那条记录。

在这里插入图片描述

现在又有一个事务ID为11的事务,要将刚才学生表中的那条记录的学生年龄改为38:

  • 因为是要进行写操作,所以需要先给该记录(最新的记录)加行锁。
  • 修改前,先将该行记录拷贝到undo log中,此时undo log中就又有了一行副本数据。
  • 然后再将原始记录中的学生年龄改为38,并将该记录的DB_TRX_ID改为11,回滚指针DB_ROLL_PTR设置成刚才拷贝到undo log中的副本数据的地址,从而指向该记录的上一个版本。
  • 最后当事务11提交后释放锁,这时最新的记录就是学生年龄为38的那条记录。

修改后的示意图如下:

在这里插入图片描述

此时我们就有了一个基于链表记录的历史版本链,而undo log中的一个个的历史版本就称为一个个的快照。

说明一下:

  1. 所谓的回滚实际就是用undo log中的历史数据覆盖当前数据,而所谓的创建保存点就可以理解成是给某些版本做了标记,让我们可以直接用这些版本数据来覆盖当前数据。
  2. 这种技术实际就是基于版本的写时拷贝,当需要进行写操作时先将最新版本拷贝一份到undo log中,然后再进行写操作,和父子进程为了保证独立性而进行的写时拷贝是类似的。

insertdelete的记录如何维护版本链?

  1. 删除记录并不是真的把数据删除了,而是先将该记录拷贝一份放入undo log中,然后将该记录的删除flag隐藏字段设置为1,这样回滚后该记录的删除flag隐藏字段就又变回0了,相当于删除的数据又恢复了。
  2. 新插入的记录是没有历史版本的,但是一般为了回滚操作,新插入的记录也需要拷贝一份放入undo log中,只不过被拷贝到undo log中的记录的删除flag隐藏字段被设置为1,这样回滚后就相当于新插入的数据就被删除了。

当前读 VS 快照读

  • 当前读:读取最新的记录,就叫做当前读。
  • 快照读:读取历史版本,就叫做快照读。

事务在进行增删查改的时候,并不是都需要进行加锁保护。事务对数据进行增删改的时候,操作的都是最新记录,即当前读,需要进行加锁保护。事务在进行select查询的时候,既可能是当前读也可能是快照读,如果是当前读,那也需要进行加锁保护,但如果是快照读,那就不需要加锁,因为历史版本不会被修改,也就是可以并发执行,提高了效率,这也就是MVCC的意义所在。

undo log中的版本链何时才会被清除?

  1. 在undo log中形成的版本链不仅仅是为了进行回滚操作,其他事务在执行过程中也可能读取版本链中的某个版本,也就是快照读。
  2. 因此,只有当某条记录的最新版本已经修改并提交,并且此时没有其他事务与该记录的历史版本有关了,这时该记录在undo log中的版本链才可以被清除。

说明一下:

  1. 对于新插入的记录来说,没有其他事务会访问它的历史版本,因此新插入的记录在提交后就可以将undo log中的版本链清除了。
  2. 因此版本链在undo log中可能会存在很长时间,尤其是有其他事务和这个版本链相关联的时候,但这也没有坏处,这说明它是一个热数据。

Read View

事务在进行快照读操作时会生成读视图 Read View,在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃的事务ID。Read View在MySQL源码中就是一个类,本质是用来进行可见性判断的,当事务对某个记录执行快照读的时候,对该记录创建一个Read View,根据这个Read View来判断,当前事务能够看到该记录的哪个版本的数据。

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;
	
	// 省略...
};

部分成员说明:

  1. m_ids: 一张列表,记录Read View生成时刻,系统中活跃的事务ID。
  2. m_up_limit_id: 记录m_ids列表中事务ID最小的ID。
  3. m_low_limit_id: 记录Read View生成时刻,系统尚未分配的下一个事务ID。
  4. m_creator_trx_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,即创建或最近一次修改该记录的事务ID,因此可以依次遍历版本链中的各个版本,通过Read View来判断当前事务能否看到这个版本,如果不能则继续遍历下一个版本。

源码策略 如下:

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));
}

使用该函数时将版本的DB_TRX_ID传给参数id,该函数的作用就是根据Read View,判断当前事务能否看到这个版本。


RR 与 RC 的本质区别

启动两个终端,将隔离级别都设置为可重复读,并查看此时银行用户表中的数据。如下:

在这里插入图片描述

在两个终端各自启动一个事务,在左终端中的事务操作之前,先让右终端中的事务查看一下表中的信息。如下:

在这里插入图片描述

左终端中的事务对表中的信息进行修改并提交,右终端中的事务看不到修改后的数据。如下:

在这里插入图片描述

在右终端中使用 select ... lock in share mode 命令进行当前读,可以看到表中的数据确实是被修改了,只是右终端中的事务看不到而已。如下:

在这里插入图片描述

但如果修改一下SQL的执行顺序,在两个终端各自启动一个事务后,直接让左终端中的事务对表中的信息进行修改并提交,然后再让右终端中的事务进行查看,这时右终端中的事务就直接看到了修改后的数据。如下:

在这里插入图片描述

在右终端中使用select ... lock in share mode命令进行当前读,可以看到刚才读取到的确实是最新的数据。如下:

在这里插入图片描述

  • 上面两次实验的唯一区别在于,右终端中的事务在左终端中的事务修改数据之前是否进行过快照读。
  • 由于RR级别下要求事务内每次读取到的结果必须是相同的,因此事务首次进行快照读的地方,决定了该事务后续快照读结果的能力

RR与RC的本质区别

  • 正是因为Read View生成时机的不同,从而造成了RC和RR级别下快照读的结果的不同。
  • 在RR级别下,事务第一次进行快照读时会创建一个Read View,将当前系统中活跃的事务记录下来,此后再进行快照读时就会直接使用这个Read View进行可见性判断,因此当前事务看不到第一次快照读之后其他事务所作的修改。
  • 而在RC级别下,事务每次进行快照读时都会创建一个Read View,然后根据这个Read View进行可见性判断,因此每次快照读时都能读取到被提交了的最新的数据。
  • RR级别下快照读只会创建一次Read View,所以RR级别是可重复读的,而RC级别下每次快照读都会创建新的Read View,所以RC级别是不可重复读的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/300285.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEAVsCode常用插件

IDEA&VsCode常用插件 IDEA lombok、mybatisx 插件 Vscode Vetur —— 语法高亮、智能感知、Emmet 等&#xff0c;包含格式化功能&#xff0c; AltShiftF &#xff08;格式化全文&#xff09;&#xff0c;CtrlK CtrlF&#xff08;格式化选中代码&#xff0c;两个 Ctrl需…

区间预测 | Matlab实现CNN-LSTM-KDE的卷积长短期神经网络结合核密度估计多变量时序区间预测

区间预测 | Matlab实现CNN-LSTM-KDE的卷积长短期神经网络结合核密度估计多变量时序区间预测 目录 区间预测 | Matlab实现CNN-LSTM-KDE的卷积长短期神经网络结合核密度估计多变量时序区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.CNN-LSTM-KDE多变量时间序列区…

Ubuntu无网络解决办法

1.进入root并输入密码 sudo su 2.更新NetworkManager的配置 用vim打开NetworkManager.conf vim /etc/NetworkManager/NetworkManager.conf 将第五行 managedFalse 改为 managedTrue 。 如果本身就是True就不用改了。 3.删除NetworkManager配置 service NetworkManager st…

el-date-picker日期时间选择器限制可选的日期范围

业务场景&#xff1a;需要限制日期时间选择器可选择的日期&#xff0c;有两种模式&#xff0c; 一种是已知范围&#xff0c;只能选已知范围内的日期&#xff0c; 另一种是知道最近天数&#xff0c;只能选今天往前的天数内的日期&#xff0c;超出不能选。 <el-date-picker v-…

Redis反序列化的一次问题

redis反序列化的一次问题 1. 问题描述 springbootredis不少用&#xff0c;但是一直没遇到什么问题&#xff0c;直接代码拷贝上去就用了。这次结合spring-security&#xff0c;将自定义的spring-security的UserDetails接口的实现类SecurityUser&#xff0c;反序列化取出时报错…

【解决】hosts文件无修改权限问题

1. 以管理员身份运行命令提示符&#xff08;cmd&#xff09;&#xff1a; 2. 在cmd中输入notepad进入记事本&#xff1a; 3. 通过记事本打开hosts文件&#xff1a; 4. 修改并保存&#xff1a;

系列六、MindManager取消首字母自动大写

一、MindManager取消首字母自动大写 1.1、步骤 主页>字体>设置字体样式>格式字体>文本和大写>文本大写>无 1.2、参考 https://tieba.baidu.com/p/3752136361

UI动效设计师通往高薪之路,AE设计从基础到进阶教学

一、教程描述 UI动效设计&#xff0c;顾名思义即动态效果的设计&#xff0c;用户界面上所有运动的效果&#xff0c;也可以视其为界面设计与动态设计的交集&#xff0c;或者可以简单理解为UI设计中的动画效果&#xff0c;是UI设计中不可或缺的组成部分。现在UI设计的要求越来越…

如何写html邮件 —— 参考主流outook、gmail、qq邮箱渲染邮件过程

文章目录 ⭐前言⭐outlook渲染邮件⭐gmail邮箱渲染邮件⭐qq邮箱渲染邮件 ⭐编写html邮件&#x1f496;table表格的属性&#x1f496;文本&#x1f496;图片&#x1f496;按钮&#x1f496;背景图片 ⭐总结⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于 …

PEFT: 在低资源硬件上对十亿规模模型进行参数高效微调

1 引言 最近&#xff0c;深度学习的研究中出现了许多大型预训练模型&#xff0c;例如 GPT-3、BERT 等&#xff0c;这些模型可以在多种自然语言处理任务中取得优异的性能表现。而其中&#xff0c;ChatGPT 模型因为在对话生成方面的表现而备受瞩目&#xff0c;成为了自然语言处理…

链表

目录 单链表 双链表 单链表 题目如下&#xff1a;模拟一个单链表&#xff0c;实现插入删除操作 解题代码 #include <iostream>using namespace std;const int N 100010;// head 表示头结点的下标 // e[i] 表示节点i的值 // ne[i] 表示节点i的next指针是多少 // idx …

vmlinux, vmlinux.bin, bzImage; cmake的find_package(Clang)新增了哪些变量( 比较两次记录的所有变量差异)

vmlinux, vmlinux.bin, bzImage cd /bal/linux-stable/ file vmlinux #vmlinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=b99bbd9dda1ec2751da246d4a7ae4e6fcf7d789b, not stripped #文件大小 20MB, 19940148Bfile ar…

小程序组件内的数据监听器

数据监听器可以用于监听和响应任何属性和数据字段的变化。从小程序基础库版本 2.6.1 开始支持。 有时&#xff0c;在一些数据字段被 setData 设置时&#xff0c;需要执行一些操作。例如&#xff0c; 一个值取决于另外两个值的变化&#xff0c;this.data.sum 永远是 this.data.…

学习笔记 | Kafka

一、概述 定义 1、Kafka传统定义&#xff1a;Kafka 是一个分布式的基于 发布/订阅模式 的消息队列&#xff08;Message Queue&#xff09; &#xff0c;主要应用与大数据实时处理领域。 2、发布/订阅&#xff1a;消息的发送者不会将消息直接发送给特定的订阅者&#xff0c;而…

localhost和127.0.0.1的区别是什么

今天在网上逛的时候看到一个问题&#xff0c;没想到大家讨论的很热烈&#xff0c;就是标题中这个&#xff1a; localhost和127.0.0.1的区别是什么&#xff1f; 前端同学本地调试的时候&#xff0c;应该没少和localhost打交道吧&#xff0c;只需要执行 npm run 就能在浏览器中打…

光速爱购--靠谱的SpringBoot项目

简介 这是一个靠谱的SpringBoot项目实战&#xff0c;名字叫光速爱购。从零开发项目&#xff0c;视频加文档&#xff0c;十天就能学会开发JavaWeb项目。 教程路线是&#xff1a;搭建环境> 安装软件> 创建项目> 添加依赖和配置> 通过表生成代码> 编写Java代码&g…

LeetCode-重复的子字符串(459)

题目描述&#xff1a; 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 思路一&#xff1a; 使用枚举的方法。首先因为字符串s有一个子串重复多次构成&#xff0c;那么s的长度len与子串的长度subLen应该成倍数关系&#xff0c;并且在s中索…

C++/OpenGL应用程序

图像应用程序大部分是 C 编写&#xff0c;OpenGL 调用实现与 3D 渲染相关任务将会使用一些扩展库: GLEW、GLM、GLFW、SOLL2 等。 GLFW 库包含 GLFWwindow 类&#xff0c;我们可以在其上进行 3D 场景绘制。OpenGL 也向我们提供了用于 GLSL 程序载入可编程着色阶段并对其进行编译…

算法第十三天-组合总和Ⅱ

组合总和Ⅱ 题目要求 解题思路 按顺序搜索&#xff0c;设置合理的变量&#xff0c;在搜索的过程中判断是否会出现重复集结果。重点理解对输入数组排序的作用和参考代码中 大剪枝和小剪枝 的意思 这道题域上一问的区别在于&#xff1a; 第39题&#xff1a;candidates中的数字…

Linux系统IO—探索输入输出操作的奥秘

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;HEART BEAT—YOASOBI 2:20━━━━━━️&#x1f49f;──────── 5:35 &#x1f504; ◀️ ⏸ ▶️ ☰ …