【MySQL】事务 【下】{重点了解读-写 4个记录隐藏列字段 undo log日志 模拟MVCC Read View sel}

1.MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种数据库管理系统(DBMS)中用来管理并发访问数据库的技术。它允许读操作不加锁地并发执行,从而提高了数据库系统的并发性能。MVCC 主要用于处理读/写冲突,使得读写操作可以同时进行,而不需要通过传统的锁机制来管理并发。

MVCC 的核心思想

MVCC 的核心思想是为数据库中的每个数据行维护多个版本,每个版本都记录了数据行的创建时间、事务ID等信息。当一个事务需要读取数据时,它会根据事务的ID和数据的版本信息来决定读取哪个版本的数据。同时,当事务需要修改数据时,它会在内部创建一个新的数据版本,而不是直接覆盖旧的数据。

MVCC 的工作原理

  1. 读操作:
    当一个事务进行读操作时,它会根据事务的ID和数据的版本信息来读取数据。
    如果事务ID小于或等于数据的创建事务ID,并且数据的删除事务ID(如果有的话)大于事务ID,那么事务可以读取这个数据版本。
    如果数据有多个版本,事务会读取“可见”的最新的版本。
  2. 写操作:
    写操作通常涉及修改现有数据或插入新数据。
    在MVCC中,写操作会创建一个新的数据版本,而不是直接覆盖旧的数据。
    新版本的数据会包含新的事务ID和创建时间等信息。
  3. 事务提交和回滚:
    当事务提交时,其修改的数据版本会变为当前有效版本,而其他事务可能无法再看到旧版本的数据。
    如果事务回滚,则其所做的修改(即新创建的数据版本)会被丢弃,系统恢复到事务开始前的状态。

MVCC 的优点

提高并发性能:通过允许多个读操作并发执行而不加锁,MVCC 显著提高了数据库的并发性能。
减少锁竞争:由于读操作不需要加锁,因此减少了读操作和写操作之间的锁竞争。
避免死锁:由于读操作不会阻塞写操作,写操作也不会阻塞读操作,因此MVCC有助于避免死锁的发生。

MVCC 的缺点

增加存储需求:由于需要维护多个数据版本,因此MVCC 会增加数据库的存储需求。
增加管理复杂性:管理多个数据版本和事务的可见性规则增加了数据库管理系统的复杂性。

常见的使用MVCC的数据库

PostgreSQL
MySQL(InnoDB存储引擎)
Oracle
SQL Server(在某些配置下)
这些数据库系统通过实现MVCC机制,提供了高并发性和良好的事务隔离性。

在RR级别,多个事务的update,多个事务的insert,多个事务的delete,是否会有加锁现象。

现象结果是,update,insert,delete之间是会有加锁现象的,但是select和这些操作是不冲突的。这就是通过读写锁(锁有行锁或者表锁)+MVCC完成隔离性。

数据库并发的场景

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

重点了解 读-写

多版本并发控制(MVCC )是一种用来解决读-写冲突的无锁并发控制。

为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。

MVCC 可以为数据库解决以下问题

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。

4个记录隐藏列字段

  1. DB_TRX_ID: 6byte,最近修改( 修改/插入)事务ID,记录创建这条记录/最后一次修改该记录的事务ID
  2. DB_ROLL_PTR: 7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就行,这些数据一般在 undo log中)
  3. DB_ROW_ID:6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键,(InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
  4. 删除flag隐藏字段,记录被更新或删除并不代表真的删除,而是删除flag变了

实验演示

mysql> create table if not exists student(
 name varchar(11) not null, 
age int not null 
);
mysql> insert into student (name, age) values ('张三', 28);
 Query OK, 1 row affected (0.05 sec)
mysql> select * from student;
 +--------+-----+
 | name   | age |
 +--------+-----+
 | 张三   |  28 |
 +--------+-----+
 1 row in set (0.00 sec)

在这里插入图片描述
我们目前并不知道创建该记录的事务ID,隐式主键,假设设置成null,1。第一条记录也没有其他版本,我们设置回滚指针为null。

2.理解事务

讲多线程时,我们讲到多个线程“同时”申请锁,再怎么同时,实际上,也是有先后顺序的!比如:先来的先获得锁。事务也是一样,我们讲事务具有原子性,即事务具有执行前和执行后两种状态,但是,我们要自己理解到,事务终究有执行中这一个状态,只不过mysql提供各种策略,使得执行中这一过程发生的并发事件安全的执行,使得在外层看来只有执行前和执行后两种状态。事务再怎么“同时”到来,终究有先后顺序。怎么分辨谁先谁后?事务ID:每个事务有一个单向增长的事务ID,ID越小来得越早。

  1. 每个事务都要有自己的事务!,可以根据事务ID的大小,来决定事务到来的先后顺序
  2. mysqd可能会面临处理多个事务的情况,事务在使用MySQL的人看来,事务是原子的,但事务自己知道,事务是有执行中这一状态的!事务也有自己的生命周期:被创建/被投递到等待队列/被取出来执行/执行错误要回滚/执行完毕要消除 ==> mysqd要对多个事务进行管理,先描述,在组织。
  3. 事务在我看来,mysqd中一定是对应的一个或者一套结构体对象/类对象,事务也要有自己的结构体。创建事务对象,申请事务ID,初始化事务对象。 链表/队列:事务队列:对事务的操作 --> 对数据结构的增删查改。表在内存的page里,删除时不用真的删除再移动其余page(线性表移动复杂度高),只要设置删除flag=0时就行,刷盘时再选择有效的刷盘。空间换时间。恢复未刷盘的数据时修改flag还能找到!

undo log日志

MySQL 将来是以服务进程的方式,在内存中运行。我们之前所讲的所有机制:索引,事务,隔离性,日志等,都是在内存中完成的,即在MySQL 内部的相关缓冲区中,保存相关数据,完成各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中的。
undo log,简单理解成,就是MySQL 中的一段内存缓冲区,用来保存日志数据的就行。

mysql日志简介

MySQL中的binlog(二进制日志)、redolog(重做日志)和undolog(回滚日志)是三种重要的日志类型,它们在MySQL数据库的运行和维护中扮演着不同的角色。以下是对这三种日志的简要叙述:

  1. binlog(二进制日志)

定义与作用:

binlog即binary log,是MySQL的二进制日志文件,也称为变更日志(update log)。它记录了所有更新数据库的语句(如DDL和DML语句),并以二进制的形式保存在磁盘中,但不包含没有修改任何数据的语句(如数据查询语句select、show等)
binlog主要用于数据恢复和数据复制(如主从复制)。通过binlog,可以恢复某一时刻的误操作数据,也是实现MySQL数据备份、集群高可用、读写分离等功能的基础。
记录格式与写入时机:

binlog有三种记录格式:statement、row和mixed。

事务执行过程中,会先把日志写到binlog cache中去,事务提交时,才把binlog cache写到binlog文件中去(刷盘)。
binlog cache是为了保证一个事务的所有操作能够一次性写入binlog不被拆开而设置的缓存,其大小受binlog_cache_size参数控制。

写入策略:

sync_binlog参数控制binlog的写入策略。
sync_binlog=0:表示每次提交事务都只write,由操作系统自行判断什么时候执行fsync。
sync_binlog=N:表示每次提交事务都只write,积攒N个事务后才执行fsync。
sync_binlog=1(默认):表示每次提交事务都会执行write和fsync,即将binlog数据持久化到磁盘中。

  1. redolog(重做日志)

定义与作用:

redo log是MySQL中用于保证事务持久性的重要机制。当MySQL服务器意外崩溃或宕机后,它能帮助恢复已经提交的事务,确保数据不会丢失。
redo log是物理日志,记录的是在某个数据页上做了什么修改,属于InnoDB存储引擎层。

写入机制:

InnoDB以页为单位来管理存储空间,修改数据时会先将整个页加载到buffer pool中,然后对需要修改的记录进行修改。但修改后的数据不会立即刷新到磁盘,因为此时刷新是随机IO且效率低下。
因此,InnoDB引入了redo log,在修改数据后,不立即刷新磁盘,而是记录一条redo log,内容是关于哪个页面、多少偏移量、什么数据发生了什么变更。
redo log是循环写入固定的文件,顺序写入磁盘,提高了性能。

刷盘时机:

redo log的刷盘时机由innodb_flush_log_at_trx_commit参数控制。
设置为1时(最安全),每次事务提交都会将redo log写入磁盘。
设置为0时,由后台线程每秒刷新一次到磁盘。
设置为2时,事务提交时只写入操作系统的文件缓存,由操作系统决定何时刷新到磁盘。

  1. undolog(回滚日志)

定义与作用:

undo log是MySQL中用于事务回滚和MVCC(多版本并发控制)的日志。它记录了事务回滚前的数据状态,当事务需要回滚时,可以通过undo log将数据恢复到事务开始前的状态。
同时,undo log也是MVCC实现的关键部分,用于保存数据的旧版本,以便支持非锁定读(如快照读)。

记录内容:

undo log只记录事务中的增删改操作,查询操作不会记录,因为查询不会修改数据。
undo日志段(undo log segment)是undo日志的集合,包含在回滚段(rollback segment)中。InnoDB支持多个回滚段,以提高并发性能。

作用:

事务回滚:保证事务的原子性,通过undo日志在事务需要回滚时,让数据恢复到最初的样子。
MVCC:作为MVCC版本链的一部分,存储老版本数据,支持非锁定读。

综上所述,binlog、redolog和undolog在MySQL中各自承担着不同的职责,共同保证了数据库的数据一致性和可靠性。
在这里插入图片描述

模拟MVCC

阶段1

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

事务10要进行修改操作,所以要先给该记录加行锁。修改前,现将该行记录拷贝到undolog中,所以,undolog中就有了一行副本数据。(原理就是写时拷贝)

所以现在 MySQL中有两行同样的记录。现在修改原始记录中的name,改成李四。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前 事务10 的ID,我们默认从 10 开始,之后递增。而原始记录的回滚指针 DB_ROLL_PTR列,里面写入undo log中副本数据的地址,从而指向副本记录,既表示我的上一个版本就是它。

事务10提交,释放锁。此时,最新的记录是’李四‘那条记录。

在这里插入图片描述

阶段2

现在又有一个事务11,对student表中记录进行修改(update):将age(28)改成age(38)。

事务11,因为也要修改,所以要先给该记录加行锁。

修改前,先将该行记录拷贝到undo log中,所以,undo log中就又有了一行副本数据。此时,新的副本,我们采用头插方式,插入undo log。

现在修改原始记录中的age,改成 38。并且修改原始记录的隐藏字段DB_TRX_ID 为当前ID。而原始记录的回滚指针事务11的DB_ROLL_PTR 列,里面写入undo log中副本数据的地址,从而指向副本记录,既表示我的上一个版本就是它。

事务11提交,释放锁。
在这里插入图片描述
这样,我们就有了一个基于链表记录的历史版本链。所谓的回滚,无非就是用历史数据,覆盖当前数据。上面的一个一个版本,我们可以称之为一个一个的快照

上面是以更新(upadte)主讲的,如果是delete呢?

一样的,别忘了,删除不是清空,而是设置flag为删除即可,也可以形成版本。

如果是insert呢?

因为insert是插入,也就是之前没有数据,那么insert也就没有历史版本。但是一般为了回滚操作,insert的数据也是要被放入undo log中(放插入前的数据,什么都没有的空表就放一个空表),如果当前事务commit了,那么这个undo log 的历史insert记录就可以被清空了。

那么select呢?

首先,select不会对数据做任何修改,所以,为select维护多版本,没有意义。

select读取,是读取最新的版本呢?还是读取历史版本?

  1. 当前读:读取最新的记录,就是当前读。增删改(insert delete update),都叫做当前读,select也有可能当前读,比如:select lock in share mode(共享锁), select for update
  2. 快照读:读取历史版本(一般而言),就叫做快照读。

多个事务同时删改查的时候,都是当前读,是要加锁的。那同时有select过来,如果也要读取最新版(当前读),那么也就需要加锁,这就是串行化。

但如果是快照读,读取历史版本的话,是不受加锁限制的。也就是可以并行执行!换言之,提高了效率,即
⇒ MVCC的意义所在。

那么,是什么决定了,select是当前读,还是快照读呢?隔离级别!那为什么要有隔离级别呢?

事务都是原子的。所以,无论如何,事务总有先有后。
经过上面的操作我们发现,事务从begin->CURD->commit,是有一个阶段的。也就是事务有执行前,执行中,执行后的阶段。但不管怎么启动多个事务,总是有先有后的。
那么多个事务在执行中,CURD操作是会交织在一起的。那么,为了保证事务的“有先有后”,是不是应该让不同的事务看到它该看到的内容,这就是所谓的隔离性与隔离级别要解决的问题。

先来的事务,应不应该看到后来的事务所做的修改呢?

不应该!按照事务到来的顺序,看到他的视野内应该看到的东西!

版本链主要是为了支持隔离性相关操作,在执行回滚时,MySQL通常这样做

上个事务执行了insert,执行的时候MySQL会保存一条delete-sql,回滚时,执行该条逆向sql即可。undo-log里的历史版本数据只在该事务运行期间有效,一旦该事务提交,这些数据就失效了!undo log里的数据并不是一直有效的。它们会在事务提交或回滚后被清理,或者在不再需要支持MVCC时被回收。然而,具体的清理时机和方式取决于多种因素,包括事务的状态、MVCC的需求、系统配置以及数据库负载等。
update/delete/insert:操作前会先将当前数据放到undolog,会在事务提交或回滚后被清理,或者在不再需要支持MVCC(其他事务访问需求)时被回收。

insert操作的具体

在MySQL中,特别是使用InnoDB存储引擎时,INSERT操作在undo log里的生命周期涉及多个阶段,这些阶段与事务的处理、MVCC的实现以及undo log的清理机制紧密相关。以下是INSERT操作在undo log中生命周期的详细解释:

  1. 事务开始
    当一个事务开始时,如果它包含INSERT操作,InnoDB会为该事务分配一个回滚段(rollback segment)和相应的undo log空间。
    分配的回滚段用于管理该事务产生的所有undo log记录,包括INSERT操作产生的undo log。
  2. INSERT操作执行
    在执行INSERT操作时,InnoDB会将新插入的数据行的副本(实际上是逻辑上的“旧版本”,因为对于INSERT操作来说,其“旧版本”就是该数据行不存在)记录在undo log中。
    这个undo log记录用于支持事务的回滚和MVCC的读取操作。
  3. 事务提交或回滚
    如果事务成功提交(COMMIT),InnoDB会标记与该事务相关的undo log记录为可清理状态(但不一定立即清理,具体取决于MVCC的需求和purge线程的工作)。
    如果事务回滚(ROLLBACK),InnoDB会使用undo log记录中的信息来撤销事务中所做的所有更改,包括INSERT操作插入的数据行。
  4. MVCC的支持
    在事务提交后,INSERT操作产生的undo log记录可能会继续保留在undo log中,以支持MVCC的读取操作。
    当其他事务需要读取该数据行的旧版本时(例如,为了执行一致性非锁定读或解决幻读问题),它们可以通过undo log找到相应的旧版本数据。
  5. Undo Log的清理
    InnoDB的purge线程负责定期清理不再需要的undo log记录。
    当确定某个undo log记录不再被任何事务需要时(例如,所有依赖于该记录的事务都已经提交,并且没有更旧的事务需要读取该记录),purge线程会将其从undo log中删除,并回收相应的空间。
  6. 总结
    INSERT操作在undo log里的生命周期包括事务开始时的分配、INSERT操作执行时的记录、事务提交或回滚时的处理、MVCC的支持以及最终的undo log清理。这个生命周期是InnoDB事务处理和多版本并发控制机制的重要组成部分。需要注意的是,undo log记录的清理并不是即时的,而是由InnoDB的内部机制和purge线程来管理的。

版本理解

之前读写并发,读提交或读未提交,体现出来的现象是,不同事务看到了不同的数据,这意味着,他们看到的不是同一份数据,之前讲这是隔离性的体现,本质是版本隔离的体现。读写可以并发的原因就在于,他们访问的不是同一份数据!也就不需要像写写那样加锁,也就不会hang住,也就实现了并发!隔离性让不同事务看到不同版本数据,隔离级别决定不同事务 看到哪些版本,不能 看到哪些版本。隔离性和事务回滚都是通过mvcc实现的!

3.Read View

事务类 – PCB
ReadView – 进程地址空间

Read View就是事务进行 快照读 操作的时候生产的 读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID,这个ID是递增的,最新的事务,ID值越大)

Read view在 MySQL源码中,就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照读的时候,对该记录创建一个Read view读视图,把它比作条件,用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo 1og 里面的某个版本的数据。

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;     // 形成快照时活跃事务ID中最小的ID
low_limit_id;    //ReadView生成时刻系统尚未分配的下一个事务ID,目前已出现过的事务ID的最大值+1
creator_trx_id   //创建该ReadView的事务ID

我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID的,即:当前记录的DB_TRX_ID。我们现在有的东西:当前快照读的 Readview 和 版本链中的某一个记录的DB_TRX_ID 。

所以现在的问题就是,当前快照读,应该读到哪个版本记录。

在这里插入图片描述

字段

在MySQL中,尤其是在使用InnoDB存储引擎的MVCC(多版本并发控制)机制时,read-view是一个非常重要的概念。它主要用于确定在某个事务执行期间,它能够“看到”哪些版本的数据行。具体来说,read-view帮助事务实现非锁定读(如SELECT语句在不加锁的情况下执行),同时保证事务的隔离级别。

read-view主要包含以下几个关键字段或组件,这些字段帮助InnoDB决定哪些行的版本对当前事务是可见的:

  1. m_ids: 一个列表,包含了在当前事务开始时刻,系统中所有活跃(未提交)的事务ID。这个列表用于判断一个事务的修改对当前事务是否可见。如果一个事务的ID在m_ids列表中,那么它所做的修改对当前事务是不可见的,除非这个事务在后续被提交。
  2. min_trx_id: 在创建read-view时,系统中当前最小的活跃事务ID(不包括当前事务自身)。这个值用于辅助决定某些版本链上更早的事务修改是否对当前事务可见。
  3. max_trx_id: 在创建read-view时,系统分配的下一个事务ID。这个值用于决定哪些事务是在read-view创建之后启动的,它们的修改对当前事务自然是不可见的。
  4. creator_trx_id: 创建这个read-view的事务的ID。这个值主要用于内部逻辑判断,确保事务自身所做的修改(即使尚未提交)对其自身总是可见的。

通过这些字段,InnoDB能够构建一个“可见性”视图,即在这个read-view下,哪些事务的修改对当前事务是可见的。这是通过比较数据行上的事务ID与read-view中的m_ids、min_trx_id和max_trx_id来实现的。如果一行数据的修改事务ID小于min_trx_id,那么修改对当前事务可见;如果修改事务ID在m_ids列表中,那么修改不可见;如果修改事务ID大于或等于max_trx_id,那么修改也是在read-view创建之后发生的,因此也不可见。

总的来说,read-view是InnoDB实现MVCC机制的关键部分,它确保了事务在读取数据时能够遵守其指定的隔离级别,同时避免了不必要的锁等待,提高了数据库的并发性能。

如果查到不应该看到当前版本,接下来就是遍历下一个版本,直到符合条件,即可以看到。上面的
readview 是当你进行select的时候,会自动形成

select lock in share mode

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

可重复读本应是两个客户端都提交后,B才能看到A更新后的数据,为什么这时候B没有提交,只有A提交了,B此时还能看到?实际上,前面说的是错误的理解!可重复读的本质,是在某段时间内。我读到的数据是一致的,是“可重复读”的,下面这种情况,符不符合?显然符合!::B快照读的时刻不同,决定他看到的数据不同(本质是看到了不同的版本)!read view形成的时机的不同,会影响事务的可见性!!!
在这里插入图片描述

在这里插入图片描述

总结:

  1. 怎么实现读写并发的?:增删改都是当前读,查看是快照读,即两类行为访问的不是同一版本数据,故不用加锁,故可以并发。
  2. 怎么实现一个事务更新了数据,另一个事务看不到?两人看到的是不用的版本数据!
  3. RR双方提交后才能看到最新数据,RC在别人提交后就能看到,怎么实现的?:readView形成的时刻不同!
  4. 事务内部怎么实现回退的?:有历史版本,有逆向操作,回滚时:事务结构体/readview对象释放+执行逆向操作。
  5. 读未提交:都是当前读,隔离性很差。串行化:当前读+加锁

MVCC流程

在这里插入图片描述

在这里插入图片描述

事务4:修改name(张三)变成name(李四)当事务2 对某行数据执行了 快照读,数据库为该行数据生成一个 Read view 读视图

//事务2的 Read View
m_ids;       	   // 1,3    
up_limit_id;      // 1
low_limit_id;     // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
creator_trx_id    // 2

此时的版本链

在这里插入图片描述

只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务。因为事务4先提交,事务2再形成快照,所以,当前事务2能看到版本链中的哪一个,我们从DB_TRX_ID=4开始找。事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID 去跟up_limit_id,low_limit_id和活跃事务ID列表(trx_list)进行比较,判断当前事务2能看到该记录的版本。

//事务2的 Read View
m_ids;           // 1,3
up_limit_id;     // 1
low_limit_id;     // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
creator_trx_id   // 2 
DB_TRX_ID=4//事务4提交的记录对应的事务ID

 //比较步骤
DB_TRX_ID(4< up_limit_id(1) ?  不小于,下一步
DB_TRX_ID(4>= low_limit_id(5) ? 不大于,下一步
m_ids.contains(DB_TRX_ID) ? 不包含,说明,事务4不在当前的活跃事务中。
故,事务4的更改,应该看到。
所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

RR与RC

--设置RR模式下测试
mysql> set global transaction isolation level REPEATABLE READ;
 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)--依旧用之前的表
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;--插入一条记录,用来测试
mysql> insert into user (id, age, name) values (1, 15,'黄蓉'); 
Query OK, 1 row affected (0.00 sec)

在这里插入图片描述
在这里插入图片描述
用例1与用例2:唯一区别仅仅是表1的事务B在事务A修改age前,快照读过一次age数据。而表2的事务B在事务A修改age前没有进行过快照读。

结论:

事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读,决定该事务后续快照读结果的能力。delete同样如此。

RR 与 RC的本质区别

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同。

在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来。此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;

即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见的。

而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。

总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。正是RC每次快照读,都会形成Read View,所以,RC才会有不可重复读问题。

优质文章

https://blog.csdn.net/SnailMann/article/details/94724197
https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html
https://blog.csdn.net/chenghan_yang/article/details/97630626
  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿猿收手吧!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值