mysql的多版本并发控制MVCC

✨博主介绍

💂 个人主页:苏州程序大白

💂 个人社区:CSDN全国各地程序猿

🤟作者介绍:中国DBA联盟(ACDU)成员,CSDN全国各地程序猿(媛)聚集地管理员。目前从事工业自动化软件开发工作。擅长C#、Java、机器视觉、底层算法等语言。2019年成立柒月软件工作室,2021年注册苏州凯捷智能科技有限公司

💅 有任何问题欢迎私信,看到会及时回复

👤 微信号:stbsl6,微信公众号:苏州程序大白

💬如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)

🎯 想加入技术交流群的可以加我好友,群里会分享学习资料

事务隔离级别

在这里插入图片描述

// 事务隔离级别
1. Serializable(串行化): 会加写锁与读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
2. RepeatableRead(可重复读): 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
3. ReadCommitted(读已提交): 一个事务提交之后,它做的变更才会被其他事务看到。
4. ReadUncommitted(读未提交): 一个事务还没提交时,它做的变更就能被别的事务看到

// 不同隔离级别的结果
不同的隔离级别下,事务A的返回结果会不同,v1、v2、v3 的返回值是什么
读未提交: v1=2、v2=2、v3=2。事务B虽然还没提交,但结果已经被A看到了,因此,v1是2,v2、v3也都是2
读提交: v1=1、v2=2、v3=2,事务B的更新在提交后才能被A看到。所以V2的值是2,v3也是2。
可重复读: v1=1,v2=1,v3=2,事务在执行期间看到的数据前后必须是一致的,所以v2=1
串行化: v1=1,v2=1,v3=2事务B执行将1改成2的时候,会被锁住,直到事务 A 提交后,事务B才可以继续执行。
所以事务A的v1、v2值是1,v3的值是 2

// 视图概念
在 mysql 里,有两个“视图”的概念
(1) 一个是 view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,而它的查询方法与表一样。
(2) 另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现

数据库会创建一个视图,不同事务访问的时候都是以视图的逻辑结果为准
在 读提交 隔离级别下,这个视图是在每个sql语句开始执行时创建的
在 可重复读 隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这一个视图
在 读未提交 隔离级别下,直接返回记录上的最新值,没有视图的概念
在 串行化 隔离级别下,直接使用加锁的方式避免并行访问,没有视图的概念

在不同的隔离级别下,数据库行为是有所不同的
Oracle 数据库的默认隔离级别是 读提交
Mysql 数据库的默认隔离级别是 可重复读
如果需要数据迁移,一定要将隔离级别设置正确

ReadViewde的实现

undolog的版本链

在这里插入图片描述

// trx_id
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。
它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的
每行数据是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。
同时,旧的数据版本要保留,并且在新的数据版本中,能够有办法可以拿到旧数据版本。
也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id

// 隐藏字段 trx_id roll_pointer
每条数据其实都有两个隐藏字段,一个是trx_id,一个是roll_pointer。
trx_id: 最近一次更新这条数据的事务id
roll_pointer: 指向更新这个事务之前生成的undo log
假设有一个事务A(id=50),插入了一条数据。
因为事务A的id是50,所以这条数据的txr_id就是50,roll_pointer指向一个空的undo log,因为之前这条数据是没有的。
接着假设有一个事务B过来修改一下这条数据,把值改成了值B,事务B的id是58。
那么此时更新之前会生成一个undo log记录之前的值,然后会让roll_pointer指向这个实际的undo log回滚日志。
接着假设事务C又来修改了一下这个值为值C,他的事务id是69,此时会把数据行里的txr_id改成69,然后生成一条undo log,记录之前事务B修改的那个值
多个事务串行执行的时候,每个事务修改了一行数据,都会更新隐藏字段txr_id和roll_pointer。

// undo log记录的是数据的前后变化
图中虚线框里是同一行数据的3个版本,当前最新版本是v3,值是值C,
它是被 trx_id=69 的事务更新的,因此这行数据版本的 row trx_id 是69
undo log记录的就是图中两个虚线u1、u2前后变化的值和执行变化的事务id
因为记录了u1、u2执行前后变化值和事务id,所以值A、值B、值C 并不是物理上真实存在的。
不是物理真实存在,而是每次需要的时候根据当前版本和 undo log 计算出来的,所以不需要拷贝数据
比如当需要 v1 的时候,就是通过 v3 依次执行 u2、u1 算出来的

undo log通过roll_pinter指针串联起来,形成一个重要的数据版本链

ReadView-基本概念

在这里插入图片描述

// undo log多版本链条实现的ReadView机制
启动一个事务时,会生成一个ReadView,里面比较关键的东西有4个
(1) m_ids: 此时有哪些事务在MySQL里执行但还没提交的,执行中但没提交的事务称为活跃事务;
(2) min_trx_id: m_ids里最小的值;
(3) max_trx_id: mysql下一个要生成的事务id,就是最大事务id;
(4) creator_trx_id: 当前这个事务的id
// 视图数组m_ids
InnoDB 为每个事务构造了一个数组,这个数组保存这个事务启动瞬间,当前正在"活跃"的所有事务 ID。
这个m_ids数组称为视图数组,"活跃"指的就是,启动了但还没提交的那些事务的id
// 高低水位 min_trx_id和max_trx_id
低水位min_trx_id: 视图数组中的事务ID的最小值记为低水位
高水位max_trx_id: 当前已经创建过的事务ID的最大值加1记为高水位,也就是mysql下一个要生成的事务id
本事务ID creator_trx_id: 当前执行的这个事务自己的id
这四个东西就组成了当前事务的一致性视图(read-view)
数据版本的可见性规则,就是基于undo log的数据版本链的 row trx_id 加上这个一致性视图的对比结果得到的

// 对比结果规则
一个数据版本的 row trx_id,对于当前事务的视图数组来说有以下几种可能
(1) 如果这个row trx_id比min_trx_id小,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的
(2) 如果这个row trx_id比max_trx_id大,表示这个版本是由以后启动的事务生成的,是肯定不可见的
(3) 如果这个row trx_id与creator_trx_id相等,说明是本事务自己的更新,可见
(4) 不符合(1)(2)(3)情况下,如果这个row trx_id在m_ids中,表示这个版本是由还没提交的事务生成的,不可见
(5) 不符合(1)(2)(3)情况下,如果这个row trx_id不在m_ids中,表示这个版本是已经提交了的事务生成的,可见

// 查找undo log数据版本链
当根据对比结果规则为不可见的情况下,那么会去找到这个数据版本的"上一个版本"
意思就是顺着这条数据的roll_pointer顺着undo log日志链条往下找
如果"上一个版本"也不符合比对结果规则,那就继续往前找,直到找到的数据版本是是符合比对结果规则的。

// RC实现ReadView机制
当一个事务设置处于RC隔离级别的时候,它是每次发起查询,都重新生成一个ReadView
如果在这次查询之前,有事务修改了数据还提交了,那么这次查询生成的ReadView里,m_ids列表不包含这个已经提交的事务了,既然不包含已经提交的事务了,那么当然可以读到提交过的修改过的值了。

// RR实现ReadView机制
当一个事务设置处于RR隔离级别的时候,它是第一次查询时生成一个ReadView,整个事务期间只使用这一个ReadView
别的事务修改数据之后哪怕提交了,也是看不到修改的值的,这就避免了不可重复读的问题。
别的事务插入了一些新的数据,也是读不到的,这样就可以避免幻读的问题。

所谓的ReadView机制,其实是基于undo log版本链条加一致性视图实现的一套读视图机制。
生成一个ReadView,之后如果是本事务自己更新的数据,自己是可以读到的,或者是在你生成ReadView之前提交的事务修改的值,也是可以读取到的。
但如果是生成ReadView的时候,就已经处于活跃的其他事务在本事务生成ReadView之后修改了数据,接着提交了,此时本事务是读不到的,或者是你生成ReadView以后再开启的事务修改了数据,还提交了,此时也是读不到的。
读ReadView的数据称之为"一致性读"。

// 一些注意事项
// 不要使用长事务
长事务意味着会存在很老的事务视图数组。
由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间
可以在 information_schema 库的 innodb_trx 这个表中查询长事务
这个语句用于查找持续时间超过 60s 的事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
// set autocommit不要设置为0
如果set autocommit=0,那么会将这个线程的自动提交关掉。
这样的话,每个SQL语句或者语句块所在的事务都需要显式的主动执行"commit"才能提交事务
设置为0,执行一个 select 语句,那这个事务就启动了,持续存在直到你主动执行 commit 或 rollback 语句或者断开连接才会结束
// 可重复读和读提交
(1)在可重复读隔离级别下,只需要在事务开始启动的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图
(2)在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图
// 事务启动的时机
begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。
事务的两种启动时机
(1) 一致性视图是在执行第一个快照读语句时创建的
(2) 一致性视图是在执行 start transaction with consistent snapshot 时创建的
如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令

ReadView-例子

在这里插入图片描述

假设数据库里原来就有一行数据,很早以前就有事务插入过了,事务id是32,它的值就是初始值
接着现在有两个事务并发过来执行了,一个是事务A(id=45),一个是事务B(id=59)
事务B是要去更新这行数据的,事务A是要去读取这行数据的值的
现在事务A会直接开启一个ReadView,这个ReadView里的m_ids就包含了事务A和事务B的两个id,也就是45和59,然后min_trx_id就是45,max_trx_id就是60,creator_trx_id就是45是事务A自己。
这时事务A第一次查询这行数据,会走一个判断,判断一下当前这行数据的txr_id是否小于我的ReadView中的min_trx_id。
此时发现txr_id=32,是小于ReadView里的min_trx_id就是45的,说明你事务开启之前,修改这行数据的事务早就提交了,所以此时可以查到这行数据。

在这里插入图片描述

接着事务B开始动手把这行数据的值修改为了值B,然后这行数据的txr_id设置为自己的id,也就是59,同时roll_pointer指向了修改之前生成的一个undo log,接着这个事务B就提交了。
这时事务A再次查询,此时查询的时候,会发现一个问题,那就是此时数据行里的txr_id=59,这个txr_id是大于我的ReadView里的min_txr_id(45),同时小于ReadView里的max_trx_id(60)的。
说明更新这条数据的事务,很可能就跟自己差不多同时开启的,于是会看一下这个txr_id=59,是否在ReadView的m_ids列表里
在事务A的ReadView的m_ids列表里,有45和59两个事务id,说明这个修改数据的事务B是跟自己同一时段并发执行然后提交的,所以对这行数据是不能查询的。
既然这行数据不能查询,那就顺着这条数据的roll_pointer顺着undo log日志链条往下找。
会找到最近的一条undo log,trx_id是32,此时发现trx_id=32,是小于ReadView里的min_trx_id(45)的,说明这个undo log版本必然是在事务A开启之前就执行且提交的,就用这个值

在这里插入图片描述

接着假设事务A自己更新了这行数据的值,改成值A,trx_id修改为45,同时保存之前事务B修改的值的快照。
此时事务A来查询这条数据的值,会发现这个trx_id=45,和自己的ReadView里的creator_trx_id(45)是一样的,
说明这行数据是自己修改的,用这个值

在这里插入图片描述

接着在事务A执行的过程中,突然开启了一个事务C,这个事务C的id是78,然后他更新了那行数据的值为值C,还提交了。
这个时候事务A再去查询,会发现当前数据的trx_id=78,大于了自己的ReadView中的max_trx_id(60),说明事务A开启之后,之后有一个事务更新了数据,对于事务A来说当然是不能看到的了
此时就会顺着undo log多版本链条往下找,自然先找到值A自己之前修改的过的那个版本,因为那个trx_id=45跟自己的ReadView里的creator_trx_id是一样的,所以此时直接读取自己之前修改的那个版本。

当前读

在这里插入图片描述

// update更新数据是当前读
事务更新数据的时候,不能从ReadView历史版本链上按照视图规则进行一致性读,而是每次读的都是最新的值。否则的话事务B的更新就丢失了。
当前读也就是数据版本链上的最新值

// select加锁也是当前读
除了 update 语句外,select 语句如果加锁,也是当前读
如果把事务 A 的查询语句 select * from t where id=1 修改一下
加上 lock in share mode 或 for update,也都是当前读,返回是值B,而不是原始值
比如下面这两个 select 语句,就是分别加了读锁(S 锁,共享锁)和写锁(X 锁,排他锁)
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

// 因为是当前读所以要等待锁
事务是当前读,必须要读最新版本,所以会导致必须加锁,因此就被锁住了,必须等到其他释放这个锁,才能继续它的当前读

💫点击直接资料领取💫

在这里插入图片描述

❤️关注苏州程序大白公众号❤️


👇 👇👇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
版本并发控制(MVCC)是MySQL中的一种技术,它通过维护数据的多个版本,以实现读写操作的并发控制MVCC通过在每行记录后面保存两个隐藏的列(一个保存行的创建时间,一个保存行的删除时间)来实现。当一个事务读取数据时,它会根据事务开始的时间戳和行的版本信息来确定可见的数据版本。这种机制在InnoDB存储引擎中被广泛使用,可以提供一致性读操作的保证。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [MySQL之InnoDB存储引擎-MVCC](https://blog.csdn.net/qq_53267860/article/details/125073612)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [MySQL数据库版本并发控制MVCC](https://blog.csdn.net/iuu77/article/details/129132863)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [mysql版本并发控制MVCC的实现](https://download.csdn.net/download/weixin_38607195/14907745)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

苏州程序大白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值