当前位置:   article > 正文

深入理解mvcc与bufferPool缓存机制_commit时bufferpool的值才更行吗

commit时bufferpool的值才更行吗

MVCC多版本并发控制机制

Mysql在可重复读隔离级别下如何保证事务较高的隔离性,上章演示过,同样的sql查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果。

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

Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。

undo日志版本链与read view机制详解

undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链  简单来说就是,原本数据库里面有一条信息,这时候有个事务来修改这行了,事务id是300,那么undo就会新增一条行,记录id,name和事务id300还有就是roll_pointer用来记录上一个事务记录的位

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

版本链比对规则:

1. 如果 row 的 trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;

2. 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若

row 的 trx_id 就是当前自己的事务是可见的);

3. 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况

    a. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自

    己的事务是可见的);

    b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

举例说明:

假设 执行顺序是由上往下依次执行 同行的同时执行,那么:

select1 第一次查询 的时候版本链如下:

看上面的图,首先几个事务同时begin,事务1进行update,紧接着,事务2和3也进行update操作,之后事务3进行commit事务,紧接着事务4进行select查询,首先按照上面说的, select1 开启事务后会在第一次查询会生成 一个一致性视图 [100,200], 300  括号里面的是所有未提交事务id数组,和一个已经提交的最大事务id(300)  这里的事务select1 第一次select,查询结果,相信大家应该已经猜到了,没错就是lilei300,

这里首先判断数组里面的事务编号是300的(原因是上图中的undo版本链条第一个值),然后跟一致性视图 [100,200], 300   对比 发现300 落在黄色里面 并且符合 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。 

所以最后返回事务id为300的name,lilei300,由于生成的视图是不变的,在可重复读隔离级别下,所以第二次select和第三次select的结果都是一样的。但是最后一个事务 select2,是在第一个事务commit之后查询所以,第一个视图查询结果就是lilei2

然后接着看第二次select 1的查询

这里首先判断数组里面的事务编号是100的,跟trx_id进行比较发现他其实是在上图中未提交与已提交事务中,这时候发现,trx_id在视图数组中,是不可见的,然后200进行比较,还是和100一样,然后用300进行比较,发现300的事务id也在未提交与已提交事务中,但是不在视图数组中,表示这个班班是已经提交的事务生成的,所以最后返回事务id为300的name,lilei300

第三个查询同样的道理 但是最后一个事务 select2,是在第一个事务commit之后查询所以,第一个视图查询结果就是lilei2

对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的

trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被

删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数

据。

注意:begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,

事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。 这里解释下

关于mysql  trx_id  

Xid 和 InnoDB 的 trx_id 是两个容易混淆的概念。

Xid 是由 server 层维护的。InnoDB 内部使用 Xid,就是为了能够在 InnoDB 事务和 server 之间做关联。但是,InnoDB 自己的 trx_id,是另外维护的。

InnoDB 内部维护了一个 max_trx_id 全局变量,每次需要申请一个新的 trx_id 时,就获得 max_trx_id 的当前值,然后并将 max_trx_id 加 1。

InnoDB 数据可见性的核心思想是:每一行数据都记录了更新它的 trx_id,当一个事务读到一行数据的时候,判断这个数据是否可见的方法,就是通过事务的一致性视图与这行数据的 trx_id 做对比。

对于正在执行的事务,你可以从 information_schema.innodb_trx 表中看到事务的 trx_id。

我在上一篇文章的末尾留给你的思考题,就是关于从 innodb_trx 表里面查到的 trx_id 的。现在,我们一起来看一个事务现场:

图 3 事务的 trx_id

session B 里,我从 innodb_trx 表里查出的这两个字段,第二个字段 trx_mysql_thread_id 就是线程 id。显示线程 id,是为了说明这两次查询看到的事务对应的线程 id 都是 5,也就是 session A 所在的线程。

可以看到,T2 时刻显示的 trx_id 是一个很大的数;T4 时刻显示的 trx_id 是 1289,看上去是一个比较正常的数字。这是什么原因呢?

实际上,在 T1 时刻,session A 还没有涉及到更新,是一个只读事务。而对于只读事务,InnoDB 并不会分配 trx_id。也就是说:

在 T1 时刻,trx_id 的值其实就是 0。而这个很大的数,只是显示用的。一会儿我会再和你说说这个数据的生成逻辑。

直到 session A 在 T3 时刻执行 insert 语句的时候,InnoDB 才真正分配了 trx_id。所以,T4 时刻,session B 查到的这个 trx_id 的值就是 1289。

需要注意的是,除了显而易见的修改类语句外,如果在 select 语句后面加上 for update,这个事务也不是只读事务。

在上一篇文章的评论区,有同学提出,实验的时候发现不止加 1。这是因为:

update 和 delete 语句除了事务本身,还涉及到标记删除旧数据,也就是要把数据放到 purge 队列里等待后续物理删除,这个操作也会把 max_trx_id+1, 因此在一个事务中至少加 2;

InnoDB 的后台操作,比如表的索引信息统计这类操作,也是会启动内部事务的,因此你可能看到,trx_id 值并不是按照加 1 递增的。

那么,T2 时刻查到的这个很大的数字是怎么来的呢?

其实,这个数字是每次查询的时候由系统临时计算出来的。它的算法是:把当前事务的 trx 变量的指针地址转成整数,再加上 248。使用这个算法,就可以保证以下两点:

因为同一个只读事务在执行期间,它的指针地址是不会变的,所以不论是在 innodb_trx 还是在 innodb_locks 表里,同一个只读事务查出来的 trx_id 就会是一样的。

如果有并行的多个只读事务,每个事务的 trx 变量的指针地址肯定不同。这样,不同的并发只读事务,查出来的 trx_id 就是不同的。

那么,为什么还要再加上 2的48次方呢?

在显示值里面加上 248,目的是要保证只读事务显示的 trx_id 值比较大,正常情况下就会区别于读写事务的 id。但是,trx_id 跟 row_id 的逻辑类似,定义长度也是 8 个字节。 因此,在理论上还是可能出现一个读写事务与一个只读事务显示的 trx_id 相同的情况。不过这个概率很低,并且也没有什么实质危害,可以不管它。

另一个问题是,只读事务不分配 trx_id,有什么好处呢?

一个好处是,这样做可以减小事务视图里面活跃事务数组的大小。因为当前正在运行的只读事务,是不影响数据的可见性判断的。所以,在创建事务的一致性视图时,InnoDB 就只需要拷贝读写事务的 trx_id。

另一个好处是,可以减少 trx_id 的申请次数。在 InnoDB 里,即使你只是执行一个普通的 select 语句,在执行过程中,也是要对应一个只读事务的。所以只读事务优化后,普通的查询语句不需要申请 trx_id,就大大减少了并发事务申请 trx_id 的锁冲突。

由于只读事务不分配 trx_id,一个自然而然的结果就是 trx_id 的增加速度变慢了。

但是,max_trx_id 会持久化存储,重启也不会重置为 0,那么从理论上讲,只要一个 MySQL 服务跑得足够久,就可能出现 max_trx_id 达到 248-1 的上限,然后从 0 开始的情况。

当达到这个状态后,MySQL 就会持续出现一个脏读的 bug,我们来复现一下这个 bug。

首先我们需要把当前的 max_trx_id 先修改成 248-1。注意:这个 case 里使用的是可重复读隔离级别。具体的操作流程如下:

图 4 复现脏读

由于我们已经把系统的 max_trx_id 设置成了 2的48次方-1,所以在 session A 启动的事务 TA 的低水位就是 2的48次方-1。

在 T2 时刻,session B 执行第一条 update 语句的事务 id 就是 2的48次方-1,而第二条 update 语句的事务 id 就是 0 了,这条 update 语句执行后生成的数据版本上的 trx_id 就是 0。

在 T3 时刻,session A 执行 select 语句的时候,判断可见性发现,c=3 这个数据版本的 trx_id,小于事务 TA 的低水位,因此认为这个数据可见。

但,这个是脏读。

由于低水位值会持续增加,而事务 id 从 0 开始计数,就导致了系统在这个时刻之后,所有的查询都会出现脏读的。

并且,MySQL 重启时 max_trx_id 也不会清 0,也就是说重启 MySQL,这个 bug 仍然存在。

那么,这个 bug 也是只存在于理论上吗?

假设一个 MySQL 实例的 TPS 是每秒 50 万,持续这个压力的话,在 17.8 年后,就会出现这个情况。如果 TPS 更高,这个年限自然也就更短了。但是,从 MySQL 的真正开始流行到现在,恐怕都还没有实例跑到过这个上限。不过,这个 bug 是只要 MySQL 实例服务时间够长,就会必然出现的。

详见 mysql XID和trx_id小结_ITPUB博客

事务开始 只要查询了 innodb的表 那么 一致性视图就生成了  后面所有的快照都基于 这份readview 

也就是说查询了 film表  然后 另外别的事务修改了accout里面id=3 的 balance的 值 3 改为38888 那么查出来的还是 film那时候 的3

也就是说undo  版本链只有一份 但是 每个事物都有自己的readView 一致性视图

总结:

MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取

同一条数据在版本链上的不同版本数据。

 innodb引擎的 大致实现:

innodb引擎 有两个日志 undo  和redo 日志

一条sql 并不是直接取ibd磁盘文件修改   先使用索引定位数据加载到缓存中去(上图中的buffer pool缓存池)   

然后 把旧的值写入undolog日志     方便回滚

接着 更新buffer pool缓存池 数据update操作等

然后 然后更新redo(重做)日志  (刚刚修改的新的值得操作)写到 buff中的(redo日志)  然后写入磁盘(redo日志) 

接着 处理binlog日志 然后准备提交事务

然后

接着随机io写入磁盘

redo日志作用:

因为 写入磁盘是随机io 并不知道什么时候写入磁盘

因为

 8 是不定时 随机刷盘 不确定 所以万一commit 提交后 然后 缓存池值更新成功但是 磁盘 数据没更新然后断电的话 那么就使用 redo日志恢复

也就是说redo 日志恢复bufferpool数据        binlog恢复磁盘数据

并且

针对 数据库磁盘数据读写 随机io读写 

针对数据库日志redo  读写 是顺序io读写 

效率 差距 两到三个数量级

为什么redo日志文件顺序io  而数据库写入磁盘是随机io

磁盘 文件没法顺序读 没法连续 因为可能有删除 然后后来数据 插入进来

磁盘 顺序读写比随机的高

如果不涉及删除那么可以顺序读写  否则只能随机读写

顺序读写 找到开头直接往下就行了  随机读写 会跳来跳去 的

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/575569
推荐阅读
  

闽ICP备14008679号