当前位置: 首页 > news >正文

【MySQL】MVCC与Read View

目录

一、数据库并发的三种场景

二、读写场景的MVCC

(一)表中的三个隐藏字段

(二)undo 日志

(三)模拟MVCC

(四)Read View

(五)当前读和快照读

三、RC和RR隔离级别的区别


一、数据库并发的三种场景

        数据库作为存储大量数据的介质,一定存在着大量的IO操作,也就是写操作和读操作。

读-读 :不存在任何问题,也不需要并发控制;

读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读;

写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失。

        本文主要讨论在读写场景下的并发问题。

二、读写场景的MVCC

        MVCC即多版本并发控制协议,其是 InnoDB 存储引擎为了实现高并发事务处理的核心机制。它通过维护数据的多个版本,使读写操作可以非阻塞并行执行,从而极大提升了数据库的并发性能。

(一)表中的三个隐藏字段

        当新建一个表结构时,除了显示定义的列结构,表中还包含了三个隐藏字段:

  • DB_TRX_ID:6字节,记录最近修改本条记录的事务ID;
  • DB_ROLL_PTR:7字节,回滚指针,记录该条记录的上一版本;
  • DB_ROW_ID:6字节,当数据表没有主键时,InnoDB会自动以 DB_ROW_ID 作为隐藏主键并建立一个聚簇索引。

        例如向一张表插入一条数据时,实际该数据内容为:

nameageDB_TRX_IDDB_ROLL_PTRDB_ROW_ID
张三18最近修改本条记录的事务IDnull(新增数据因此没有上个版本)1(隐藏主键)

        实际数据表还有个删除 flag 隐藏字段,用于表明该条是否有效,也就是删除表中数据时是逻辑删除,之后在合适的时候由 MySQL 再向磁盘刷新数据。

(二)undo 日志

        undo 日志是在 MySQL 中的一段内存缓冲区,用于保存日志文件。其主要由两个核心作用:

  • 事务回滚:
    当事务执行失败或者主动回滚时,undo log 中记录的数据旧版本课用于恢复原始状态。例如:当执行插入数据操作时,undo log 会记录其对应的删除操作,回滚时直接执行该操作删除数据;当执行更新或删除操作时,undo log会记录数据的旧值用于回滚。
    事务在修改数据前,undo log会记录反向操作或数据旧值,形成逻辑日志链。
  • 支持MVCC:
    undo log会存储数据的历史版本,通过隐藏字段 DB_TRX_ID 和 DB_ROLL_PTR串联历史版本,形成链式日志从而支持 MVCC 。详见下文。

(三)模拟MVCC

        假定 student 表中已有数据如下:

        假定有个事务10,对 student 表中记录进行了修改,将姓名修改为了"李四"。当事务10执行完毕后:

        在此过程中,事务10首先会将该条记录加行锁,修改前先将该条记录拷贝到 undo log 中(写时拷贝)。之后将数据修改为目标值并再填写相应的字段,之后事务提交后并释放锁。

        假定现在有个事务11对表中数据进行修改,将年龄改为了20,事务11也会进行以上的操作。当事务11执行完毕后:

        undo log中的一个个版本被称为快照。正如上文所述,除了记录版本链以外,undo log 还会记录相反的操作以备回滚。

        当执行插入操作时,undo log 会基于主键记录对应相反的删除操作;当执行删除操作时会将该记录的删除 flag 字段设置为删除即逻辑删除,并将该条数据记录在 undo log 中;当执行select 操作时,会根据隔离级别执行当前读或者快照读。当前读即读取最新的数据,快照读即读取数据的历史版本。

        针对于 select 操作,在RU隔离级别下所有查询都是读取最新版本的数据,RC和RR隔离级别下所有普通查询都是快照读,而Serializable隔离级别下事务是严格串行执行,因此所有查询操作都是当前读。本文主要讨论如何MVCC 机制如何解决 RC 和 RR隔离级别下的读写并发问题。

        隔离级别和读写并发问题详见:【MySQL】事务及隔离性-CSDN博客

(四)Read View

        Read View 是事务首次进行快照读时由 MySQL 生成的,其主要是配合 MVCC 机制进行版本控制。

        当某个事务执行 select 快照读的时候,MySQL新建一个 Read View 对象,用其内部的字段来判断当前事务应该读取数据的哪个版本,该数据可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据,这由隔离级别决定。

        以下是简化 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;//ids_t集合类型 /** 配合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,也就是目前已出现过的事务ID的最大值+1
creator_trx_id:创建该ReadView的事务ID

        表中的三个隐藏字段配合 Read View 即可完成 MVCC机制。

        当一个事务进行快照读时,MySQL 会为此建立一个 Read View 对象,首先 up_limit_id 字段会记录当前活跃事务的最小事务ID,low_limit_id 会记录当前活跃时最大事务ID值 + 1, 而 m_ids 会记录当前所有活跃的事务ID。例如:当事务10创建 Read View 对象时,假定活跃事务有(8,9,12),那么事务是10对应的字段值分别为:up_limit_id = 8, low_limit_id = 13, m_ids = (8, 9, 12)。

        下面将展开说明 Read View 是如何配合 undo log 实现 MVCC机制的:

        当一个新事务执行普通 select 操作(快照读)时,MySQL 会为此新建并初始化一个 Read View 对象,针对目标数据存在以下的情况:

        若该条记录的 DB_TRX_ID 小于 Read View 中的 up_limit_id 最小事务ID,说明修改该记录的事务在新事务到来之前就已经执行完毕提交了,故该数据可被新事物所见,无需查看该条记录的上一版本了;

        若该条记录的 DB_TRX_ID 大于等于 Read View 中的 low_limit_id 最大事务ID,说明修改该记录的事务在新事务执行查询操作之后才执行完毕(不一定提交),若该条记录存在上一版本,则需通过该条记录的 DB_ROLL_PTR 字段查询上一版本并再次进行比较;若不存在上一版本,则该条记录不可被新事务所见;

        若该条记录的 DB_TRX_ID 处于 up_limit_id 与 low_limit_id 之间,则需要进一步判断。若该条记录的 DB_TRX_ID 存在于 m_ids 列表中,则说明新事务执行查询操作时 DB_TRX_ID 该事务仍处于活跃状态,因此该条记录不可见,需查询该记录上一版本进一步进行判断;若该条记录的 DB_TRX_ID 不存在于 m_ids 列表中,则说明新事务执行查询操作时 DB_TRX_ID 该事务已经执行完毕提交了,则该条记录可以被新事务所见,无需查看该条记录的上一版本了。

(五)当前读和快照读

        在上文中我们铺垫了当前读和快照读的概念,那么应该如何操作呢?以下示例都是在 RR 隔离级别下进行测试。

        两个事务同时开启,由上面两张图可知,MySQL会为快照读建立 Read View 对象,因此不同的读取可能会造成不同的查询结果。(图一是修改年龄为20,但查询结果为18/图二是修改年龄为18,查询结果也为18)

        当前读与快照读:

//当前读
mysql> select * from student lock in share mode;
//快照读
mysql> select * from student;

三、RC和RR隔离级别的区别

        RC和RR隔离级别下在快照读时都会生成 Read View 对象,正是生成 Read View 对象的时机不同,导致快照读的结果不同

在RC隔离级别下:

        每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因,也就是每个快照读都会生成最新的 Read View 对象。正是RC每次快照读,都会形成Read View,所以,RC才会有不可重复读问题

在RR隔离级别下:

        同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。也就是只有第一次进行快照读时才会生成 Read View 对象,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可 见;

相关文章:

  • SpringBoot WebMvcConfigurer使用Jackson统一序列化格式化输出
  • 带配额的文件系统 第21次CCF-CSP计算机软件能力认证
  • 数字人革新教育:开启智慧教学新时代
  • Linux系统编程收尾(35)
  • 零硬件成本玩转嵌入式通信!嵌入式仿真实验教学平台解锁STM8S串口黑科技
  • keepalived定制日志bug
  • 轻量级swiper插件推荐
  • 2025陕西省赛补题
  • [python] 最大公约数 和 最小公倍数
  • Dungeon Master(POJ-2251)
  • 现代密码学入门 | 现代密码学核心特点介绍
  • DeepSeek-R1 重磅升级,智能体验再进化!
  • antDesignVue中a-upload上传组件的使用
  • 算法打卡第11天
  • 小工具合集
  • 无人机视角海上漂浮物检测与人员救援检测数据集VOC+YOLO格式2903张6类别
  • 2024 CKA模拟系统制作 | Step-By-Step | 18、题目搭建-备份还原Etcd
  • sward V1.1.4版本发布,支持文档审批及文档导出
  • day40python打卡
  • Linux研学-入门命令
  • 什么免费推广网站好/有创意的营销案例
  • 德州谁会做网站/个人发布信息的免费平台
  • 网站页脚写什么/百度关键字
  • 除了dz论坛还能搭建什么网站/百度云网盘搜索引擎入口
  • 合肥公司网站设计/免费网页制作平台
  • 做销售平台哪个网站好/深圳关键词推广