MVCC与BufferPool缓存机制

概述

MVCC(Multi-Version Concurrency Control)机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥,而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。
Mysql在读已提交可重复读隔离级别下都实现了MVCC机制


一、Undo日志版本链与Read View机制

undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链.

read-view机制 在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

注意:begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的,也就是执行select是不会生成事务id的只有执行update insert delete 才会生成

事务1、2、3开启(通常事务id是连续的)
并且假设数据库有
事务1:update test set c1=‘123’ where id=1;//事务id假设为100
事务2:update test set c1=‘666’ where id=5;//事务id假设为200
事务3:update test set name=‘lilei300’ where id=1;commit//事务id假设为300

这时候开启一个新的session去执行select语句
如 select name from account where id=1
根据read-view的解释未提交事务数组+最大的事务组成如上面上个中所示
事务1、事务2未提交,事务3id最大因此read-view为 [100,200] ,300
且查出来的name为lilei300.

问:查询name为lilei300的原理?
答案:mysql会拿到read-view里面数组的最小事务id去作为min_id
然后再拿到所有事务中最大的id去作为max_id,然后凡是小于min_id的事务id都归到已提交事务区域里,大于max_id归纳到未开始事务(将来可以有的)
单独有一个区域把未提交事务与已提交事务都划分进去,因此三个区域分别为
已经提交事务(里面事务id均小于最小未提交事务id)
未提交事务与已经提交事务(0-最大提交事务id)
未开始事务(最大事务id-正无穷)
当下一次select开始的时候,会根据undo日志链取数据
链为 1 lilei300 300 --------> 1 lilei 60(假设一开始)
先拿到lilei300 以及事务id300 然后根据区域划分去找 发现在
未提交事务与已提交事务区域 拿到read-view [100,200],300
判断300为数组之外的300即为可见,因此name=事务id为300的那条数据
lilei300

假设这时候事务1 在执行sql操作
update test set name=‘lilei1’ where id=1;
update test set name=‘lilei2’ where id=1;
问:再执行查询,name值为多少,执行undo日志链如何判断值

答:这时候undo日志链为
1 lilei2 100 ------> 1 lilei1 100-----> 1 lilei300 300—> 1 lilei 60
readView为[100,200],300
按版本链走 发现 事务id100 比对发现在数组里 为不可见,再走发现仍未100,仍在数组里不可见继续走,发现300在数组外为最大事务id取lilei300为name

假设这时候事务id100的事务提交且事务200将name在依次改为lilei3 lilei4
那么同上根据undo日志链去走name仍为lilei300,

每一个事物的readView都有一份,而undo日志只有一份。

二、Innodb引擎SQL执行的BufferPool缓存机制

代码如下(示例):

update t set name='666' where id=1

1.根据索引从磁盘中找到id为1的数据,加载到缓存buffer pool中去
2.将这条数据的旧值存入undo日志(失败可以回滚,上面已经讲过)
3.更新缓存中的数据把旧值—>666
4.写入到redo日志的缓存里面(操作等等一系列)
5.准备提交事务(执行commit操作),redo日志缓存操作写入磁盘.
6.准备提交事务(执行commit操作),service层中的binlog日志写入磁盘(所有引擎偶有写binlog日志)。
7.写commit标记写入到redo日志里面去,主要是为了保证redo日志与binlog日志数据一致(binlog是为了恢复磁盘的数据用的,所以是一定要保证写成功的)
8.随机写入磁盘,以page为单位写入(innodb后台会有一个io线程去不定时的随机写磁盘)

问:为什么mysql要有写入redo日志的操作?
答:假设一个场景当1-7步操作都做完之后实际上只有缓存buffer pool中的值被改为666,磁盘中还是旧值,这时候如果数据库挂掉了,但是java已经认为了commit成功了。这样数据就丢失了,因此有了redo日志,当数据库重启的时候mysql会用redo日志里面的数据恢复到buffer poll里面去,再给予后台线程不定时写入磁盘。

问:mysql为什么要设计这么复杂的机制流程?
答:效率问题,这样设计可以让操作直接基于缓存buffer poll操作。如果直接基于磁盘进行增上改查的话,由于数据存储在磁盘上是随机存放的(随机IO),而日志里面是顺序存放的(顺序IO媲美内存操作),顺序IO与随机IO的效率差异巨大,2-3个数量积。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值