[MySQL]MySQL事务特性

[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 不支持。

可以查看数据库引擎来获取是否支持事务的信息:

image-20230719175436520

  • Engine: 存储引擎名。
  • Support: 服务器对存储引擎的支持级别。其中YES表示引擎受支持且处于活动状态、DEFAULT表示就像一样YES,但这是默认引擎、NO表示不支持引擎、DISABLED表示支持引擎,但已将其禁用。
  • Comment: 存储引擎描述。其中InnoDB的描述是支持事务、行级锁、外键。
  • Transactions: 是否支持事务。
  • XA: 存储引擎是否支持XA事务。
  • Savepoints: 是否支持事务保存点。

4. 事务的提交方式

事务的提交方式常见的有两种

  • 自动提交
  • 手动提交

查看事务自动提交是否打开:

show variables like 'autocommit';
  • 结果为ON表示自动提交已打开。
  • 结果为OFF表示自动提交已关闭。

image-20230719181112809

事务自动提交的打开与关闭:

SET AUTOCOMMIT=0; #禁止自动提交

SET AUTOCOMMIT=1; #开启自动提交

image-20230719181553787

5. 事务常见操作方式

测试准备:

为了更好地对事务进行实验,做了以下准备:

  • 将mysql的默认隔离级别设置成读未提交:

image-20230719185620376

设置完之后需要重启客户端才能生效。

  • 查看事务隔离级别:

image-20230719185741090

  • 创建测试表:

image-20230719190000249

image-20230719191600414

  • 启动两个MySQL客户端:

image-20230719191644330

事务正常操作测试1:

  • 在两个客户端内输入start transaction/begin开启事务:

image-20230719191800044

  • 一个客户端设置保存点然后插入数据,另一个客户端查看表中数据:

  • 重复前一步操作

image-20230719192308281

  • 重复前一步操作

image-20230719192343803

  • 插入数据的客户端回滚到s3保存点,另一个客户端查看数据:

image-20230719192606155

由于s3保存点设置在插入第三条记录之前,因此回滚到s3保存点第三条记录会消失。

  • 插入数据的客户端回滚到s1保存点,另一个客户端查看数据:

image-20230719192722713

由于s1保存点设置在插入第一条记录之前,因此回滚到s1保存点之前插入的所有记录都会消失。

  • 输入commit提交事务,查看表中数据:

image-20230719193445094

事务正常操作测试2:

  • 在两个客户端内输入start transaction/begin开启事务:

image-20230719193121648

  • 一个客户端然后插入数据,另一个客户端查看表中数据:

image-20230719193242561

  • 对插入数据的事务回滚:

由于没有保存点,会直接回滚到事务开始,因此插入的数据都会消失。

  • 输入commit提交事务,查看表中数据:

image-20230719193445094

事务正常操作测试3:

  • 在两个客户端内输入start transaction/begin开启事务:

image-20230719193121648

  • 一个客户端然后插入数据,另一个客户端查看表中数据:

image-20230719193909352

  • 输入commit提交事务,查看表中数据:

image-20230719193935721

  • 对插入数据的事务回滚,查看表中数据:

image-20230719194007643

事务已经提交了,回滚操作就无法生效了。

事务异常操作测试1:

  • 查看表中数据和事务自动提交是否打开:

  • 在两个客户端内输入start transaction/begin开启事务:

image-20230719200638034

  • 在一端插入数据,另一端查看数据:

image-20230719200801385

  • 输入crtl+\使插入数据端崩溃,然后在另一端查看数据:

image-20230719201100362

事务未提交,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交),因此插入的数据消失了。

事务异常操作测试2:

  • 查看表中数据和事务自动提交是否打开:

  • 在两个客户端内输入start transaction/begin开启事务:

image-20230719202728117

  • 在一端插入数据,另一端查看数据:

image-20230719202658614

  • 关闭插入数据端,然后在另一端查看数据:

image-20230719202845470

事务未提交,客户端关闭,MySQL自动会回滚(隔离级别设置为读未提交),因此插入的数据消失了。

事务异常操作测试3:

  • 查看表中数据和事务自动提交是否打开:

image-20230719203323152

  • 在两个客户端内输入start transaction/begin开启事务:

image-20230719203347587

  • 在一端插入数据,另一端查看数据:

image-20230719203418593

  • 关闭插入数据端崩溃,然后在另一端查看数据:

image-20230719203509320

事务提交了,客户端崩溃,MySQL数据不会在受影响,已经持久化。

说明:

  • 手动输入start transaction/begin打开事务,就要手动提交。(关闭自动提交以上的测试结果是相同的)
  • 事务运行结果一旦提交,操作结果就变成持久性的了。

事务异常操作测试4:

  • 关闭事务自动提交:

image-20230719211412809

  • 一端不手动开始事务,一段手动开始事务查看表中数据:

  • 在不开启事务的端口删除数据,在开始事物的端口查看数据:

  • 在删除数据端输入ctrl + \使其崩溃,然后在另一端查看数据:

image-20230719211813408

  • 将手动开启事务端提交,查看数据:

事务异常操作测试5:

  • 打开事务自动提交:

image-20230719212144815

  • 一端不手动开始事务,一段手动开始事务查看表中数据:

image-20230719212210197

  • 在不开启事务的端口删除数据,在开始事物的端口查看数据:

image-20230719212238225

  • 在删除数据端输入ctrl + d(效果和使客户端崩溃相同)关闭客户端,然后在另一端查看数据:

image-20230719212307694

  • 将手动开启事务端提交,查看数据:

image-20230719212403631

说明:

  • 没有手动输入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客户端就相当于开始一个会话,会话的生命周期是从启动客户端到关闭客户端。
  • 会话隔离级别和全局隔离级别不同,会话会使用当前会话隔离级别。

查看全局隔离级别:

image-20230720141400256

查看会话隔离级别:

image-20230720141547356

设置事务的隔离级别

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}  -- 设置当前会话 or 全局隔离级别

设置当前会话的隔离级别测试:

  • 设置当前会话的隔离级别为读提交:

image-20230720142150375

  • 查看当前会话的全局隔离级别和会话隔离级别:

image-20230720142413012

  • 使用在原会话修改隔离级别前开始的另一个会话查看当前会话的全局隔离级别和会话隔离级别:

image-20230720142610198

由于原会话修改的当前会话的隔离级别,因此不影响新的会话的隔离级别。

设置全局隔离级别测试:

  • 设置全局隔离级别为串行化:

image-20230720143410912

  • 查看当前会话的全局隔离级别和会话隔离级别:

image-20230720143448563

  • 使用在原会话修改隔离级别前开始的另一个会话查看当前会话的全局隔离级别和会话隔离级别:

image-20230720143646736

  • 重启一个在原会话修改隔离级别后开始的新会话查看当前会话的全局隔离级别和会话隔离级别:

image-20230720143837623

设置全局隔离级别后重启的客户端会话隔离级别才会跟着改变。

读未提交(Read Uncommitted)

读未提交是所有的事务都可以看到其他事务没有提交的执行结果。

  • 几乎没有加锁,虽然效率高,但是问题太多。

读未提交测试

  • 启动两个会话查看会话隔离级别:

  • 两个会话同时手动开启事务:

image-20230720151552068

  • 一个会话操作数据,另一个会话并发的查看数据:

image-20230720154645623

rollback就相当于事务结束了。

  • 查看数据的会话的事务提交后查看数据:

测试出现的现象: 一个事务在执行中,读到另一个执行中事务的数据操作但是未commit的数据,这种现象叫做脏读 (dirty read)。

读提交(Read Committed)

读提交是事务只能看到其他的已经提交的事务所做的改变。

读提交测试:

  • 启动两个会话查看会话隔离级别:

image-20230720153904328

  • 两个会话同时手动开启事务:

image-20230720153948626

  • 一个会话操作数据,另一个会话并发的查看数据:

image-20230720154341137

  • 查看数据的会话的事务提交后查看数据:

image-20230720154406924

测试出现的现象: 同一个事务内,同样的读取,在不同的时间段 (依旧还在事务操作中),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)。

可重复读(Repeatable Read)

可重复读是同一个事务,在执行 中,多次读取操作数据时,会看到同样的数据行。

可重复读测试:

  • 启动两个会话查看会话隔离级别:

image-20230720165828673

  • 两个会话同时手动开启事务:

image-20230720172013163

  • 一个会话操作数据,另一个会话并发的查看数据:

image-20230720172805759

  • 查看数据的会话的事务提交后查看数据:

image-20230720172837402

补充一下: insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,叫做幻读 (phantom read)。MySQL在可重复读情况不会出现幻读。

可串行化(Serializable)

串行化是强制事务排序,多个事务必须串行化执行,使事务之间不可能相互冲突。

  • 串行化读的时候加共享锁,也就是其他事务可以并发读,但是不能写,该事务自身可以写。写的时候加排它锁,其他事务不能并发写也不能并发读。

可串行化测试:

  • 启动两个会话查看会话隔离级别:

image-20230720182053601

  • 两个会话同时手动开启事务:

image-20230720172013163

  • 两个会话都进行数据查询:

image-20230720184409244

  • 一个会话进行写操作,另一个会话查询数据:

image-20230720184547742

  • 查询数据的会话提交事务:

image-20230720184710556

隔离级别总结

对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时使得数据库的数据保持一致。

给出如下表结构和表数据:

image-20230721134142443

实际在MySQL中的表结构和数据:

nameaegDB_TRX_IDDB_ROW_IDDB_ROLL_PTR
张三289(示例)1(示例)null(示例)

Undo日志:

Undo日志时MySQL 中的一段内存缓冲区,用来保存日志数据的。

  • Undo日志记录的是历史版本数据。
  • Undo日志中记录的历史版本数据不能被修改。
  • Undo日志会自动判断数据记录是否会有事务使用,如果判断没有事务会使用,就会清除记录。

模拟MVCC

现在有一个事务10,对student表中记录进行修改 – 将name(张三)改成 name(李四):

  • 事务10,因为要修改,所以要先给该记录加行锁。
  • 对记录进行操作前,先将要操作的记录拷贝到Undo日志中,使得Undo日志增加了一条历史版本的数据。
  • 在修改后的记录中的回滚指针属性字段中记录在Undo日志中拷贝的修改前的记录的地址。
  • 事务10提交,释放锁。

image-20230721135732398

说明:

  • **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中来判断该事务是否已经提交。

示意图如下:

image-20230721191846189

根据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示例

给出以下记录:

nameaegDB_TRX_IDDB_ROW_IDDB_ROLL_PTR
张三28null1null

给出如下事务操作(表格从上到下代表时间线):

事务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

此时的版本链:

image-20230721192952999

事务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:

  • 启动两个会话,查看当前隔离级别是否为可重复读:

image-20230721195211806

  • 查看表结构和表中数据:

image-20230721195804925

  • 两个会话都手动开始事务进行快照读操作:

image-20230721200110594

  • 一个会话进行修改操作,另一个会话进行快照读操作:

image-20230721200239037

  • 进行修改操作的会话提交事务,另一个会话进行快照读操作:

image-20230721200357226

  • 在未提交事务的会话中进行当前读:

image-20230721200553181

select * from user lock in share mode ,以加共享锁方式进行读取,对应的就是当前读。

测试示例2:

  • 启动两个会话,查看当前隔离级别是否为可重复读:

image-20230721195211806

  • 查看表结构和表中数据:

image-20230721201550425

  • 两个会话都手动开始事务,一个会话进行修改操作:

image-20230721201840871

  • 进行修改操作的会话提交事务,另一个会话进行快照读和当前读操作:

image-20230721202112329

在只进行查询操作的会话中,由于事务中多次读取的结果相同,因此不违背可重复读的隔离级别。

测试结论:

  • 事务中快照读的结果是非常依赖该事务首次出现快照读的地方。

RR 与 RC的本质区别

  • 在RR级别下的某个事务只有第一次快照读时生成Read View,此后该事务都使用的是这个Read View,因此事务看不到之后的修改,实现可重复读。
  • 在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。

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

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

相关文章

【力扣算法20】之 8. 找出字符串中第一个匹配项的下标 (python方向)

文章目录 问题描述示例1示例2提示 思路分析代码分析完整代码详细分析运行效果截图调用示例运行结果 完结 问题描述 给你两个字符串 haystack 和 needle &#xff0c;请你在haystack字符串中找出needle字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 n…

htmlCSS-----浮动

目录 前言&#xff1a; 浮动 1.浮动的效果 2.浮动的特点 3.浮动的写法 4.浮动的原理 5.浮动的作用 6.案例 7.浮动的缺陷与解决方式 浮动问题 解决方式 8.补充说明 前言&#xff1a; 浮动是html里面重要的一部分&#xff0c;前面我们学习了三种元素的类型&#xff08;…

计科web常见错误排错【HTTP状态404、导航栏无法点开、字符乱码及前后端数据传输呈现、jsp填写的数据传到数据库显示null、HTTP状态500】

web排错记录 在使用javaweb的过程中会出现的一些错误请在下方目录查找。 目录 错误1&#xff1a;HTTP状态404——未找到 错误2&#xff1a;导航栏下拉菜单无法点开的问题 错误3&#xff1a;字符乱码问题 错误4&#xff1a;jsp网页全部都是&#xff1f;&#xff1f;&#x…

Golang并发控制

开发 go 程序的时候&#xff0c;时常需要使用 goroutine 并发处理任务&#xff0c;有时候这些 goroutine 是相互独立的&#xff0c;需要保证并发的数据安全性&#xff0c;也有的时候&#xff0c;goroutine 之间要进行同步与通信&#xff0c;主 goroutine 需要控制它所属的子gor…

Linux系统安装部署MySQL完整教程(图文详解)

前言&#xff1a;最近网上翻阅了大量关于Linux安装部署MySQL的教程&#xff0c;在自己部署的时候总是存在一些小问题&#xff0c;例如&#xff1a;版本冲突&#xff0c;配置失败和启动失败等等&#xff0c;功夫不负有心人&#xff0c;最后还是安装部署成功了&#xff0c;所以本…

Ubuntu 网络配置指导手册

一、前言 从Ubuntu 17.10 Artful开始&#xff0c;Netplan取代ifupdown成为默认的配置实用程序&#xff0c;网络管理改成 netplan 方式处理&#xff0c;不在再采用从/etc/network/interfaces 里固定 IP 的配置 &#xff0c;配置写在 /etc/netplan/01-network-manager-all.yaml 或…

文本预处理——文本处理的基本方法

目录 什么是分词jieba分词特性精确模式分词全模式分词搜索引擎模式分词使用用户自定义词典 命名实体识别词性标注 什么是分词 jieba分词特性 精确模式分词 import jieba content工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作 print(jieba.lcut(co…

一文了解Python中的while循环语句

目录 &#x1f969;循环语句是什么 &#x1f969;while循环 &#x1f969;遍历猜数字 &#x1f969;while循环嵌套 &#x1f969;while循环嵌套案例 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;Python从入门到精通专栏 循环语句是什…

SpringBoot创建和使用

1.Spring Boot的优点 快速集成框架&#xff0c;Spring Boot提供了启动添加依赖的功能&#xff0c;用于秒级成各种框架。内置运行容器&#xff0c;无需配备Tomcat等Web容器&#xff0c;直接运行和部署程序。快速部署项目&#xff0c;无需外部容器即可启动并运行项目。可以完全抛…

概率论和随机过程的学习和整理--番外15,如何计算N合1的合成数量问题?

目录 1 目标问题&#xff1a;多阶2合1的合成问题 1.1 原始问题 1.2 合成问题要注意&#xff0c;合成的数量 1.3 合成问题不能用马尔科夫链来解决 2 方案1&#xff1a;用合成公式合成多次能解决吗&#xff1f; --不能&#xff0c;解决不了递归的问题 3 方案2&#xff0c;…

idea2021.安装pojie教程

1、下载ideaIU-2021.3应用包&#xff0c;点击finish 2、先关闭idea窗口&#xff0c;等会激活了脚本再运行打开。 3、双击运行install-current-user.vbs&#xff0c;等待一会会提示运行成功。 4、运行后&#xff0c;在文件中会多出一条配置 5、打开运行idea,输入激活码&#x…

如何使用curl下载github代码

首先通过chrome打开想要下载的源文件 如图&#xff0c;有那个下载图标时表示不需要鉴权即可下载&#xff0c;一般仓库都会开放只读权限&#xff0c;所以很大概率都有 比如我想下载这个crc32.c文件 那么我就需要知道它在哪个IP中&#xff0c;按下F12打开网络&#xff0c;点击下载…

pytorch安装GPU版本 (Cuda12.1)教程

使用本教程前&#xff0c;默认您已经安装并配置好了python3以上版本 1. 去官网下载匹配的Cuda Cuda下载地址 当前最高版本的Cuda是12.1 我安装的就是这个版本 小提示&#xff1a;自定义安装可以只选择安装Cuda Runtime。Nvidia全家桶不必全部安装。把全家桶全部安装完直接系统…

ChatGPT助力校招----面试问题分享(十二)

1 ChatGPT每日一题&#xff1a;运算放大器与比较器的区别 问题&#xff1a;运算放大器与比较器的区别 ChatGPT&#xff1a;运算放大器和比较器都是电子电路中常用的模拟电路元件&#xff0c;但它们的设计和应用略有不同。下面是两者的主要区别&#xff1a; 功能不同&#xf…

Java集合之List

ArrayLsit集合 ArrayList集合的特点 ArrayList集合的一些方法 ①.add(Object element) 向列表的尾部添加指定的元素。 ②.size() 返回列表中的元素个数。 ③.get(int index) 返回列表中指定位置的元素&#xff0c;index从0开始。 public class Test {public static void m…

【Vue】day03-VueCli(脚手架)

day03 一、今日目标 1.生命周期 生命周期介绍 生命周期的四个阶段 生命周期钩子 声明周期案例 2.综合案例-小黑记账清单 列表渲染 添加/删除 饼图渲染 3.工程化开发入门 工程化开发和脚手架 项目运行流程 组件化 组件注册 4.综合案例-小兔仙首页 拆分模块-局部…

有名管道(FIFO)的学习笔记

文章目录 有名管道介绍有名管道的使用创建 注意事项 有名管道介绍 有名管道的使用 创建 命令&#xff0c; mkfifo name函数&#xff0c;int mkfifo(const char *pathname, mode_t mode); 设置错误号&#xff1b; 向管道中写数据&#x1f447;&#xff1a; 从管道读数据&am…

前端Web实战:从零打造一个类Visio的流程图拓扑图绘图工具

前言 大家好&#xff0c;本系列从Web前端实战的角度&#xff0c;给大家分享介绍如何从零打造一个自己专属的绘图工具&#xff0c;实现流程图、拓扑图、脑图等类Visio的绘图工具。 你将收获 免费好用、专属自己的绘图工具前端项目实战学习如何从0搭建一个前端项目等基础框架项…

log4j--动态打印日志文件到指定文件夹

文章目录 log4j--动态打印日志文件到指定文件夹1、添加Maven依赖2、配置文件 log4j.properties3、编写日志打印工具类 LogUtil4、工具类调用 log4j–动态打印日志文件到指定文件夹 1、添加Maven依赖 <!-- log4j日志相关坐标 --><dependency><groupId>org.s…

API Testing 一个基于 YAML 文件的开源接口测试工具

目录 前言&#xff1a; 如何使用&#xff1f; 本地模式 服务端模式 文件格式 后续计划 前言&#xff1a; API Testing 是一个基于 YAML 文件的开源接口测试工具&#xff0c;它可以帮助开发者快速地进行接口测试。 在选择工具时&#xff0c;可以从很多方面进行考量、对比…