数据库多版本读场景

2023-10-31

session 1 session 2
select a from test; return a = 10  
start transaction;  
update test set a = 20;  
  start transaction;
  select a from test; return ?
commit;  
  select a from test; return ?

我们看下上面这个数据库日常操作的例子。

  • session 1修改了一条记录,没有提交;与此同时,session 2 来查询这条记录,这时候返回记录应该是多少呢?

  • session 1 提交之后 session 2 查询出来的又应该是多少呢?

由于MySQL支持多种隔离级别,这个问题是需要看session2的事务隔离级别的,情况如下:

  • 隔离级别为 READ-UNCOMMITTED 情况下: 
    session 1 commit前后 session 2 去查看都会看到的是修改后的结果 a = 20

  • 隔离级别为 READ-COMMITTED 情况下: 
    session 1 commit 前查看到的还是 a =10 , commit之后看到的是 a = 20

  • 隔离级别为 REPEATABLE-READ, SERIALIZABLE 情况下: 
    session 1 commit前后 session 2 去查看都会看到的是修改后的结果 a = 10

其实不管隔离级别,我们也抛开数据库中的ACID,我们思考一个问题:众所周知,InnoDB的数据都是存储在B-tree里面的,修改后的数据到底要不要存储在实际的B-tree叶子节点,session2是怎么做到查询出来的结果还是10,而不是20列?

MVCC实现原理

上述现象在数据库中大家经常看到,但是数据库到底是怎么实现的,深究的人就不多了。

其实原理很简单,数据库就是通过UNDO和MVCC来实现的。

通过DB_ROLL_PT回溯查找数据历史版本

  • 首先InnoDB每一行数据还有一个DB_ROLL_PT的回滚指针,用于指向该行修改前的上一个历史版本

图片描述

当插入的是一条新数据时,记录上对应的回滚段指针为NULL。

图片描述

更新记录时,原记录将被放入到undo表空间中,并通过DB_ROLL_PT指向该记录。session2查询返回的未修改数据就是从这个undo中返回的。MySQL就是根据记录上的回滚段指针及事务ID判断记录是否可见,如果不可见继续按照DB_ROLL_PT继续回溯查找。

通过read view判断行记录是否可见

具体的判断流程如下:

RR隔离级别下,在每个事务开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view) 
RC隔离级别下,在每个语句开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view) 
并按照以下逻辑判断事务的可见性。

图片描述

MVCC解决了什么问题

  • MVCC使得数据库读不会对数据加锁,select不会加锁,提高了数据库的并发处理能力;
  • 借助MVCC,数据库可以实现RC,RR等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了ACID中的I-隔离性。

MySQL代码分析

前面我们介绍了什么是MVCC,以及它解决了什么问题。

下面我们来看一下在MySQL源码中,到底是怎么实现这个逻辑的。

InnoDB隐藏字段源码分析

InnoDB表中会存有三个隐藏字段,这三个字段是mysql默认帮我们添加的。我们可以通过代码中查看到:

dict_table_add_system_columns(
/*==========================*/
dict_table_t*   table,  /*!< in/out: table */
mem_heap_t* heap)   /*!< in: temporary heap */
{
ut_ad(table);
ut_ad(table->n_def == (table->n_cols - table->get_n_sys_cols()));
ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
ut_ad(!table->cached);
/* NOTE: the system columns MUST be added in the following order
(so that they can be indexed by the numerical value of DATA_ROW_ID,
etc.) and as the last columns of the table memory object.
The clustered index will not always physically contain all system
columns.
Intrinsic table don't need DB_ROLL_PTR as UNDO logging is turned off
for these tables. */
dict_mem_table_add_col(table, heap, "DB_ROW_ID", DATA_SYS,
      DATA_ROW_ID | DATA_NOT_NULL,
      DATA_ROW_ID_LEN);
#if (DATA_ITT_N_SYS_COLS != 2)
#error "DATA_ITT_N_SYS_COLS != 2"
#endif
#if DATA_ROW_ID != 0
#error "DATA_ROW_ID != 0"
#endif
dict_mem_table_add_col(table, heap, "DB_TRX_ID", DATA_SYS,
      DATA_TRX_ID | DATA_NOT_NULL,
      DATA_TRX_ID_LEN);
#if DATA_TRX_ID != 1
#error "DATA_TRX_ID != 1"
#endif
if (!table->is_intrinsic()) {
dict_mem_table_add_col(table, heap, "DB_ROLL_PTR", DATA_SYS,
      DATA_ROLL_PTR | DATA_NOT_NULL,
      DATA_ROLL_PTR_LEN);
#if DATA_ROLL_PTR != 2
#error "DATA_ROLL_PTR != 2"
#endif
/* This check reminds that if a new system column is added to
the program, it should be dealt with here */
#if DATA_N_SYS_COLS != 3
#error "DATA_N_SYS_COLS != 3"
#endif
}
}
  • DB_ROW_ID:如果表中没有显示定义主键或者没有唯一索引则MySQL会自动创建一个6字节的row id存在记录中
  • DB_TRX_ID:事务ID
  • DB_ROLL_PTR:回滚段指针

InnoDB判断事务可见性源码分析

MySQL中并不是根据事务的事务ID进行比较判断记录是否可见,而是根据每一行记录上的事务ID进行比较来判断记录是否可见。

我们可以通过实验验证 , 创建一张表里面插入一条记录:

dhy@10.16.70.190:3306  12:25:47 [dhy]>select * from dhytest;
+------+
| id   |
+------+
|   10 |
+------+
1 row in set (7.99 sec)

手工开启一个事务 更新一条记录 但是并不提交:

dhy@10.10.80.199:3306  15:28:24 [dhy]>update dhytest set id = 20;
Query OK, 3 rows affected (40.71 sec)
Rows matched: 3  Changed: 3  Warnings: 0

在另外一个会话执行查询:

dhy@10.16.70.190:3306  12:38:33 [dhy]>select * from dhytest;

这时我们可以跟踪调试mysql 查看他是怎么判断记录的看见性,中间函数调用太多列举最重要部分。

这里需要介绍一个重要的类 ReadView,Read View是事务开启时当前所有事务的一个集合,这个类中存储了当前Read View中最大事务ID及最小事务ID。

/** The read should not see any transaction with trx id >= this
value. In other words, this is the "high water mark". */
trx_id_t    m_low_limit_id;
/** The read should see all trx ids which are strictly
smaller (<) than this value.  In other words, this is the
low water mark". */
trx_id_t    m_up_limit_id;
/** trx id of creating transaction, set to TRX_ID_MAX for free
views. */
trx_id_t    m_creator_trx_id;

当我们执行上面的查询语句时,跟踪到主要函数如下:

函数row_search_mvcc->lock_clust_rec_cons_read_sees
bool
lock_clust_rec_cons_read_sees(
/*==========================*/
const rec_t*    rec,    /*!< in: user record which should be read or
passed over by a read cursor */
dict_index_t*   index,  /*!< in: clustered index */
const ulint*    offsets,/*!< in: rec_get_offsets(rec, index) */
ReadView*   view)   /*!< in: consistent read view */
{
ut_ad(index->is_clustered());
ut_ad(page_rec_is_user_rec(rec));
ut_ad(rec_offs_validate(rec, index, offsets));
/* Temp-tables are not shared across connections and multiple
transactions from different connections cannot simultaneously
operate on same temp-table and so read of temp-table is
always consistent read. */
//只读事务或者临时表是不需要一致性读的判断
if (srv_read_only_mode || index->table->is_temporary()) {
ut_ad(view == 0 || index->table->is_temporary());
return(true);
}
/* NOTE that we call this function while holding the search
system latch. */
trx_id_t    trx_id = row_get_rec_trx_id(rec, index, offsets); //获取记录上的TRX_ID这里需要解释下,我们一个查询可能满足的记录数有多个。那我们每读取一条记录的时候就要根据这条记录上的TRX_ID判断这条记录是否可见
return(view->changes_visible(trx_id, index->table->name)); //判断记录可见性
}

下面是真正判断记录的看见性。

bool changes_visible(
trx_id_t    id,
const table_name_t& name) const
MY_ATTRIBUTE((warn_unused_result))
{
ut_ad(id > 0);
//如果ID小于Read View中最小的, 则这条记录是可以看到。说明这条记录是在select这个事务开始之前就结束的
if (id < m_up_limit_id || id == m_creator_trx_id) {
return(true);
}
check_trx_id_sanity(id, name);
//如果比Read View中最大的还要大,则说明这条记录是在事务开始之后进行修改的,所以此条记录不应查看到
if (id >= m_low_limit_id) {
return(false);
} else if (m_ids.empty()) {
return(true);
}
const ids_t::value_type*    p = m_ids.data();
return(!std::binary_search(p, p + m_ids.size(), id)); //判断是否在Read View中, 如果在说明在创建Read View时 此条记录还处于活跃状态则不应该查询到,否则说明创建Read View是此条记录已经是不活跃状态则可以查询到
}

对于不可见的记录都是通过row_vers_build_for_consistent_read函数查询UNDO构建老版本记录,直到记录可见。

这里需要说明一点 不同的事务隔离级别,可见性的实现也不一样:

  • READ-COMMITTED 
    事务内的每个查询语句都会重新创建Read View,这样就会产生不可重复读现象发生

  • REPEATABLE-READ 
    事务内开始时创建Read View , 在事务结束这段时间内 每一次查询都不会重新重建Read View , 从而实现了可重复读。

参考资料:

  • 《唐成-2016PG大会-数据库多版本实现内幕.pdf》

来源:沃趣科技(woqutech) 
作者:董红禹

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据库多版本读场景 的相关文章

  • Visual Studio(vs)与Pycharm,IDEA中常用快捷键汇总

    目录 Visual Studio 窗口管理 搜索 在编辑器中查找 代码编辑器 工具栏 调试 常用 Pycharm IDEA中 Ctrl 快捷键 Alt 快捷键 Shift 快捷键 Ctrl Alt 快捷键 Ctrl Shift 快捷键 Al
  • C++11 ——— 可变参数模板

    文章目录 可变参数模板的概念 可变参数模板的定义方式 参数包的展开方式 递归展开参数包 逗号表达式展开参数包 STL容器中的emplace相关接口函数 可变参数模板的概念 可变参数模板是C 11新增的最强大的特性之一 它对参数高度泛化 能够
  • nginx php站点配置文件,Nginx配置文件详解及多站点配置

    Nginx配置文件详解 配置文件在 etc nginx nginx conf nginx root目录修改报错403解决办法 chmod R 755 目录 usr share nginx html 修改nginx用户为其所有者 chown
  • 前腾讯员工不堪房价回老家进国企!享受清闲日子,但担心又随之而来

    在互联网大厂拿命挣高薪VS回老家进国企提前养老 哪个才是更好的生活 一位前腾讯员工辞职后回老家省会进了国企 过着清闲舒服的日子 却担心再这么下去自己就废了 真是个上进的小哥哥 到手18W 工作量巨少 每天在办公室都不知道干嘛 下午五点就下班
  • 硬盘的三大种类

    文章目录 一 传统硬盘 HDD Hard Disk Drive 二 固态硬盘 SSD Solid State Drive 三 混合硬盘 hybrid hard drive HHD 四 对比 一 传统硬盘 HDD Hard Disk Driv
  • 实验2linux进程控制与通信,实验2 进程控制与通信管理word文档良心出品

    实验2 进程控制与通信管理word文档良心出品 由会员分享 可在线阅读 更多相关 实验2 进程控制与通信管理word文档良心出品 13页珍藏版 请在人人文库网上搜索 1 年级11级专业计算机科学与技术班级五班组号12组实验室9 205日期2
  • ChatGPT?听说Biying把它下架了

    ChatGPT被玩疯了 开始放飞自我 ChatGPT版微软必应上线不到10天 就被网友玩坏了 先说这个词 放飞自我 什么东西才会放飞自我 人放飞自我 人 你确定是人 所以让我们来把上面的句子改写一下 什么东西才会放飞自我 人才会放飞自我 所
  • HTML5实现音频和视频嵌入

    简介 HTML5未出来之前 在线的音频和视频都是借助Flash或者第三方工具实现的 现在HTML5也支持了这方面的功能 在一个支持HTML5的浏览器中 不需要安装任何插件就能播放音频和视频 原生的支持音频和视频 为HTML5注入了巨大的发展
  • [脑科学]-这才是心理学

    这本书出了第9版了 不长 写的挺好 国内也有一本 同样的名字 隋xx写的 文笔也很好 可惜不太有名 1 心理学 就是研究大脑是如何工作的学科 嗯 一定要明白这一点 各种心理学实验就是黑盒测试大脑 脑外科就是白盒研究大脑 要想变得聪明之类的
  • 欢聚集团 -- 前端一面

    面试形式 牛客网面试 一面40多分钟 自我介绍 面试内容 手写事件委托 手写AJAX IE怎么处理 常见的http状态码 和 的区别 js隐式转换 闭包 闭包的结构 原型 原型链 对象的深浅拷贝 深拷贝的实现 怎么获取js对象的自身属性 j
  • Android多个fragment懒加载的坑(卡顿)

    在Android开发中 有很多种情况都是viewpage fragment左右滑动进行布局的 例如下面的今日头条 在今日头条app上 我们可以看到 其顶部都是一栏的类型数据源 可滑动 可联动下面的viewpage里的fragment 但是很
  • django配置DJANGO_SETTINGS_MODULE

    DJANGO SETTINGS MODULE 使用Django时要通知Django当前使用的是哪个配置文件 可以改变环境变量 DJANGO SETTINGS MODULE 实现这一点 当我们使用manage py运行起本地的web服务器时

随机推荐

  • 常见的代码优化技术

    常见的代码优化技术有 复写传播 删除死代码 强度削弱 归纳变量删除 如果有同学有研究过这部分内容 并总结过 有更好的答案 请联系我删除本篇非原创解答 1 复写传播 a d e b d e c d e gt t d e a t b t c t
  • 专治疑难系列 - 解决win11中xbox开机自启问题

    博客主页 Passerby Wang的博客 CSDN博客 系统运维 云计算 Linux基础领域博主 所属专栏 专治疑难系列 上期文章 专治疑难系列 无法激活网络的解决方法 如觉得博主文章写的不错或对你有所帮助的话 还望大家多多支持呀 关注
  • RabbitMQ与KafKa区别

    在应用场景方面 RabbitMQ 遵循AMQP协议 由内在高并发的erlanng语言开发 用在实时的对可靠性要求比较高的消息传递上 kafka是Linkedin于2010年12月份开源的消息发布订阅系统 它主要用于处理活跃的流式数据 大数据
  • 中国知网html如何复制,中国知网如何快速导出参考文献的格式? 来看看吧

    写论文时 需要很多引文及注释 就需要把参考文献标注上 按照参考文献的引文格式一点一点整理很麻烦 涉及大量文献时费时费力 中国知网有快速导出参考文献格式的功能 可单个导出 也可以大量导出 方便实用 工具 材料 中国知网 论文 操作方法 01
  • idea彻底改java项目名(详细说明各个地方改名的作用)

    需求分析 要将手头的java项目文件夹改名 经常会出现以下问题 无法运行 只是改了别名 打war包 jar包的包名还是不变 解决方案 以下将项目名Easyfile改为驼峰命名的EasyFile 改项目名称 此时只改了别名 改文件夹名 解决编
  • Yolov3中Anchor box和confidence的理解(公式未编辑 待续....)

    Anchor box bounding box prior Anchor box原理解释 1 Anchor box其实就是从训练集的所有ground truth 中通过统计聚类的方法 k mean 得到常用的尺寸大小 比如 在某个训练集中最
  • MySQL有哪些锁

    这次 来说说 MySQL 的锁 主要是 Q A 的形式 看起来会比较轻松 在 MySQL 里 根据加锁的范围 可以分为全局锁 表级锁和行锁三类 全局锁 全局锁是怎么用的 要使用全局锁 则要执行这条命 flush tables with re
  • flutter 自定义组件-抽奖大转盘

    import dart async import dart math import dart ui as ui import dart ui import package demo widget luck luck entity dart
  • TypeScript 变量声明 —— 类型断言(Type Assertion)

    类型断言 Type Assertion TypeScript 允许你覆盖它的推断 并且能以任何你想要的方式分析它 这种机制被称为类型断言 类型断言使用 as 关键字或者
  • 查出反向木马的反向连接域名

    来源 ttian net 反向木马的主要种植手段是通过IE的众多漏洞 bt下载时不小心运行 或者来路不明的软件 使未打补丁的用户点击之后下载运行了木马程序 而这些用户基本都是拥有动态IP的个人用户 若不使用反向连接的方式 势必无法长久控制
  • k8s中创建pv和pvc

    1 创建一个pv apiVersion v1 kind PersistentVolume metadata name pv0003 名称 spec capacity storage 5Gi 卷大小 volumeMode Filesystem
  • Unity中自动识别串口以及热拔插

    最近需要在Unity中实现自动识别热拔插并识别串口的功能 实在没有找到原生的消息响应 折腾了一周 尝试了多种方法 总结一下主要有3种实现思路 1 利用Form类中的消息进行串口拔插消息的接收 2 实时保存串口信息到外部文件 进行判别 3 读
  • Vscode中安装 n 命令来切换 node 版本以适应不同项目不同的node版本号

    一 问题描述 Centos中第一次安装的node 因为下载的源码是最新的 是最新的版本18 14 0的 但发现项目启动的时候提示 二 解决办法 这时候会有两个选择 卸载node然后重新安装符合项目的版本 比较麻烦 有时候可能node还卸载不
  • 踩坑道路之——ubuntu下pt query digest无法分析慢查询日志

    刚才在使用pt query digest分析慢查询日志的时候 当我优雅的敲出 sudo pt query digest var lib mysql yang K45VD slow log 此时终端并没有像我所希望的那样 现实慢查询的分析结果
  • 浏览器内核,user-agent

    最近web界被红芯事件吵得沸沸扬扬 也激起了我对浏览器内核进一步的学习热情 先来看看user agent 它是我们前端开发获取用户操作系统 浏览器版本等数据的常用方法 UA存在于每次http请求的请求头中 像这样 Mozilla 5 0 W
  • 鸽子学统计

    文章目录 第一部分 基础统计 0 统计学的目的和本质 0 1 随机变量 0 2 统计分析的目的 0 3 统计学的本质 1 描述统计 1 1 变量的测量尺度分类 1 2 均值 1 3 众数和中位数 1 4 极差和标准差 1 5 偏度和峰度 1
  • linux:argument list too long的解决方案

    问题 展示 删除的文件数目过多时 linux命令会报错 如下 rm 命令 rm txt zsh argument list too long rm ls命令 ls txt zsh argument list too long ls 原因 猜
  • Outlook无需API开发连接钉钉群机器人,实现新增会议日程自动发送群消息通知

    Outlook用户使用场景 在企业中 会议和活动的顺利举行对于业务运转和团队协作至关重要 然而 计划的变动总是无法避免 这可能会导致其他人的计划受到影响 打乱原有的安排 为了解决这个问题 许多企业开始使用各种工具和技术来确保信息的及时传递和
  • 【计算机网络】MAC帧和PPP帧(定义+使用范围+区别+共同点)

    目录 0 前言 1 PPP的定义 1 1 点对点协议PPP Point to Point Protocol PPP 1 2 PPP帧 1 3 PPP帧的格式 1 3 1 首部 1 3 2 尾部 2 MAC的定义 2 1 媒体接入控制层MAC
  • 数据库多版本读场景

    session 1 session 2 select a from test return a 10 start transaction update test set a 20 start transaction select a fro