Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制


前言

上一章,提到了MVCC,但只是一笔带过。这一章较为详细的同读者一起了解MVCC是如何实现可重复读的。同时,也学习一下存储过程,了解mysql为何又高效,又保证安全。文章的阅读,一次性可能没办法全看懂(当然也是我的问题,因为我不知道怎么安排顺序最好),或许需要先看MVCC原理部分,回头再看定义会更好,也仅仅是一种建议,我个人对枯燥的文字不是很感兴趣。


数据库并发场景三种

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

丢失更新

  • 事务A撤销时,把已经提交的事务B的更新数据覆盖了
  • 事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失

MVCC

MVCC(Multi Version Concurrency Control的简称)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。快照读本身也是一个抽象概念。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的。

与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的优势:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。

多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行。

在内部实现中,InnoDB通过undolog可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。

MVCC的实现原理

隐式字段

每行记录除了自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • DB_ROW_ID: 6byte,隐含的自增ID(隐藏主键)
    如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
    实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
  • DB_TRX_ID: 6byte,最近修改(修改/插入)事务ID
    记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR: 7byte,回滚指针
    指向这条记录的上一个版本(存储于rollback segment里)

在这里插入图片描述

undo日志

undo log主要分为两种:

  • insert undo log: 事务在insert新记录时产生的undo log
    只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log: 事务在进行update或delete时产生的undo log
    不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

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

在这里插入图片描述

Read View(读视图)

事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大。

当某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据

Read View遵循可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护)。

如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较。

即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

版本链比对规则

  • 如果 row 的 trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;
  • 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若 row 的 trx_id 就是当前自己的事务是可见的);
  • 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况
    • 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的);
    • 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
      在这里插入图片描述

流程分析

下图为4个事务。
在这里插入图片描述

在select1执行第一条查询语句select name from account where id = 1时,日志版本链如下。
在这里插入图片描述

此时的read-view是:[100, 200],300。100是数组中最小的事务id,100和200是未提交的事务id,此时最大的事务id是300, 由这三个事务id构成了此时的read-view。

根据上面的比对规则,首先会从undo日志的最新记录开始查询,查询当前这个事务到底应该读到哪一条数据记录。此时最新的记录的事务id是300,300不在活跃的视图数组中,根据比较规则,这个版本是已经提交了的事务生成的,可见。因此此时select1查询到的结果是lilei300

当select1执行第二次查询时,日志版本链为
在这里插入图片描述
此次查询的事务在之前已经生成了read-view之后这个事务也没有进行修改操作,则它的read-view依然保持不变。 即此时的read-view: [100. 200] ,300.。

  • lilei2, 100的数据,100在read-view的活跃事务数组中,属于未提交的事务,则对该记录不可见,向上判断。
  • lilei1, 100的数据,依然不可见,继续向上查询。
  • lilei300,300的可见,即此时查询到的结果为lilei300。

第三次查询时,undo版本链为
在这里插入图片描述
此次查询的事务在之前已经生成了read-view之后这个事务也没有进行修改操作,则它的read-view依然保持不变。 即此时的read-view: [100. 200] ,300.。

  • lilei4,200,200在活跃事务数组中,此条记录不可见,继续向上查询。
  • lilei3, 200的数据,依然不可见,继续向上查询。
  • lilei2, 100的数据,依然不可见,继续向上查询。
  • lilei1, 100的数据,依然不可见,继续向上查询。
  • lilei300,300的可见,即此时查询到的结果为lilei300。

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

数据库中的数据实际上最终都是要存放在磁盘文件上的,数据库执行增删改操作的时候,不可能直接更新磁盘上的数据。磁盘进行随机读写操作,速度相当慢,一个大磁盘文件的随机读写操作,要几百毫秒,每秒只能处理几百个请求。

Innodb维护了一个缓存区域叫做Buffer Pool,用来缓存数据和索引在内存中。Buffer Pool可以用来加速数据的读写,在对数据库执行增删改操作的时候,实际上主要都是针对内存里的Buffer Pool中的数据进行的,如下图所示。

如果Buffer Pool越大,那么Mysql就越像一个内存数据库。
在这里插入图片描述

  • 将磁盘数据加载到缓存池中
    加载都是一页一页加载的,如加载id为1的记录所在的整页数据
  • undo日志,写入更新数据的旧值,便于回滚。
  • 增删改查,更新内存数据,同时写redo日志
  • 准备提交事务,redo日志写入磁盘
    在数据库的内存里执行了一堆增删改的操作,内存数据是更新了,如果这个时候如果数据库突然崩溃了,没关系,只要从redo log日志文件里读取出来之前做过增删改操作,瞬间就可以重新把这些增删改操作在你的内存里执行一遍,恢复出来之前做过的增删改操作。
  • 准备提交事务,binlog日志写入磁盘
  • 写入commit标记到热动日志文件中,提交事务完成。
  • 随机写入磁盘,还是一页一页写入。
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MySQL的多版本并发控制(MVCC)是一种并发控制机制,它主要是为了解决并发读写冲突的问题。在MVCC机制中,每个事务都可以看到数据库中的一个快照,这个快照是在事务开始时确定的。事务读取数据时,实际上是读取了该快照中的数据,而不是实际的数据。当事务需要修改数据时,MySQL会根据数据的版本号来判断是否可以进行修改。 MVCC的实现原理主要是在每一行数据后面保存多个版本号,并且还需要保存该版本号对应的事务ID。当开始一个事务时,MySQL会为该事务分配一个唯一的事务ID,该事务ID会被用于标记事务对应的数据版本号。当一个事务需要读取数据时,MySQL会根据该事务的事务ID和版本号来判断是否允许读取该数据。如果该事务的事务ID小于等于该数据的版本号,那么就可以读取该数据。如果该事务需要修改数据,则MySQL会为该数据在数据库中创建一个新版本,并将该新版本版本号和事务ID保存下来。这样,其他事务就可以继续读取原来的版本,而该事务则可以读取新版本并修改数据,从而实现并发控制。 需要注意的是,MVCC只能解决读写冲突的问题,而不能解决写写冲突的问题。此外,MVCC也会占用一定的存储空间,因为每个数据行都需要保存多个版本号和事务ID。因此,在使用MVCC机制时,需要注意存储空间和性能方面的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值