MVCC 多版本并发控制

解决多个事务同时读写数据时的冲突问题,主要用于实现事务的两个隔离级别:

  • 读已提交 (Read Committed)

  • 可重复读 (Repeatable Read) (这是 MySQL InnoDB 默认的隔离级别)

【可以理解为主要是实现读已提交,只能读已经被事务提交后产生的版本,在实现读已提交的基础上,将read view生成时机从每一次select之后改成只是第一次select的时候生成read view,后面的select不再生成新的read view,就可以实现可重复读了】

实现原理:版本链(每一行数据有一个版本链,历史版本数据组成的链)+readview

1.版本链:

在每行数据中加了三个隐藏字段,这前两个隐藏字段可以帮助构造一个版本链

  1. DB_TRX_ID: 最近一次修改这行数据的事务ID。

  2. DB_ROLL_PTR: 回滚指针,指向这行数据上一个版本在 undo log 中的位置。

3.DB_ROW_ID: 隐藏的行ID,当表没有主键时,InnoDB会用它来生成聚簇索引(这个不用关注)。

sql 复制代码
account表:

//假设99号事务创建了这条记录,undo log里面存储
 ROW: {id:1, name:'张三', balance:1000}    HIDDEN: {DB_TRX_ID: 99, DB_ROLL_PTR: null}  
 
 
 // 第一次修改,101号事务将余额改成800 
 ROW: {id:1, name:'张三', balance:800}  HIDDEN: {DB_TRX_ID: 101, DB_ROLL_PTR: --> 指向undo_log_v1} 
                                   |
                                   | (DB_ROLL_PTR)
                                   v
  undo_log_v1: {balance:1000, DB_TRX_ID: 99, DB_ROLL_PTR: null}   
 
 
 //第二次修改,102事务将余额改成500(即使事务还没提交,只要执行了update语句undo log的版本链就会有这个版本)
 ROW: {id:1, name:'张三', balance:500}     HIDDEN: {DB_TRX_ID: 102, DB_ROLL_PTR: --> 指向undo_log_v2}       
                                   |
                                   | (DB_ROLL_PTR)
                                   v
undo_log_v2: {balance:800, DB_TRX_ID: 101, ROLL_PTR: -->undo_log_v1}   
                                   |
                                   | (DB_ROLL_PTR)
                                   v                                    
undo_log_v1: {balance:1000, DB_TRX_ID: 99, DB__ROLL_PTR: null}  
2.Read view:

此时一个新的事务103查询余额,为了决定它能看到哪个版本,数据库会为它生成一个 Read View:

sql 复制代码
creator_trx_id: 103          -- 创建这个Read View的事务ID   
 m_ids: [102]    --创建时,数据库里所有未提交的事务ID列表  (假设事务101已提交,事务102正在运行)  
 min_trx_id: 102    -- m_ids列表中的最小值
 max_trx_id: 104     -- 创建时,数据库下一个将要分配的事务ID         

把所有事务分成了三类:

  • 已提交的"过去": 事务ID < min_trx_id (小于102) 的,比如事务99和101。

  • 未知的"未来": 事务ID >= max_trx_id (大于等于104) 的。

  • 正在发生的"现在"(但是还没有提交): 事务ID 在 min_trx_id 和 max_trx_id 之间,并且在 m_ids 列表里(即事务102)。

3.遍历版本链中每个版本,和read view做比对

核心就是:只能读已经提交的数据,不能读还没提交的数据,可以读的数据是已经提交的版本(<max_trx_id,而且不在 m_ids列表里面的事务 id 创建的数据

事务103拿着它的 Read View,去读取张三那行数据对应的版本链,然后和版本链上每个节点进行比较:

sql 复制代码
ROW: {id:1, name:'张三', balance:500}     HIDDEN: {DB_TRX_ID: 102, DB_ROLL_PTR: --> 指向undo_log_v2}       
                                   |
                                   | (DB_ROLL_PTR)
                                   v
undo_log_v2: {balance:800, DB_TRX_ID: 101, ROLL_PTR: -->undo_log_v1}   
                                   |
                                   | (DB_ROLL_PTR)
                                   v                                    
undo_log_v1: {balance:1000, DB_TRX_ID: 99, DB__ROLL_PTR: null}  


链条上的第一个节点:这个版本是102事务创建的
(1)是我103事务创建的吗,不是
(2) 比min_trx_id小吗,不是
(3)比max_trx_id大吗,不是
 (4) m_ids: [102] 里面有102吗,有
结论:102在 m_ids 列表,说明这个版本是在我创建read view时还没提交,因此我不能看这个版本的数据


链条上的第二个节点:这个版本是101事务创建的
"101比 min_trx_id (102) 小吗?" -> 是!
说明创建这个版本的事务已经提交了,因此可以看到这条数据
4.如何实现读已提交和可重复读

按照上面的实现,已经可以实现读已提交

将read view生成时机从每一次select之后改成只是第一次select的时候生成read view,后面的select不再生成新的read view,就可以实现可重复读了

5.既然可重复读这么容易实现,为什么还会存在读已提交这个隔离级别呢?
  • "可重复读"为了维持那个"不变的快照",是有成本的。对于一个长事务,只要这个事务不结束,这个旧版read view就要一直保存,这样如果要读很多行的数据,这样就要保存很多read view文件(读已提交则可以随时删掉,下一次select的时候会重新生成最新的),造成undo log文件非常大,增加数据库后台清理线程 (purge thread) 的工作负担

  • "读已提交"并非"可重复读"的降级版,而是一种不同的设计选择。它放宽了对一致性的要求,以换取更高的数据新鲜度(可以看到更实时的数据)、更好的并发性能和更低的系统资源消耗。

相关推荐
运维行者_4 分钟前
APM 性能监控是什么?从应用监控与网站监控了解基础概念
网络·数据库·云原生·容器·kubernetes·智能路由器·运维开发
全栈小55 分钟前
【数据库】当InfluxDB遇到天花板:金仓数据库如何重构时序性能极限?
数据库·重构
颜颜yan_18 分钟前
时序数据库性能较量:金仓数据库如何在高负载场景中领跑InfluxDB
数据库·时序数据库
北友舰长19 分钟前
基于Springboot+thymeleaf快递管理系统的设计与实现【Java毕业设计·安装调试·代码讲解】
java·spring boot·mysql·校园管理·快递·快递系统
IT_陈寒24 分钟前
Vue3性能优化实战:这5个技巧让我的应用加载速度提升了40%
前端·人工智能·后端
长征coder24 分钟前
SpringCloud服务优雅下线LoadBalancer 缓存配置方案
java·后端·spring
yfs102425 分钟前
PostgreSQL 16 + pgvector 完整安装和内网访问指南(Ubuntu 20.04)
数据库·ubuntu·postgresql
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ33 分钟前
查询MySQL数据库表的结构和数据
数据库·mysql·oracle
ForteScarlet37 分钟前
Kotlin 2.3.0 现已发布!又有什么好东西?
android·开发语言·后端·ios·kotlin
Json____38 分钟前
springboot框架对接物联网,配置TCP协议依赖,与设备通信,让TCP变的如此简单
java·spring boot·后端·tcp/ip