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

文章目录

  • 前言
    • 数据库并发场景三种
    • 丢失更新
  • MVCC
  • MVCC的实现原理
    • 隐式字段
    • undo日志
    • Read View(读视图)
    • 版本链比对规则
    • 流程分析
  • Innodb引擎SQL执行的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日志串联起来形成一个历史记录版本链

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

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已提交的事务生成的,这个数据是可见的;
  • 如果 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 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
      Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制_第2张图片

流程分析

下图为4个事务。
Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制_第3张图片

在select1执行第一条查询语句select name from account where id = 1时,日志版本链如下。
Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制_第4张图片

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

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

当select1执行第二次查询时,日志版本链为
Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制_第5张图片
此次查询的事务在之前已经生成了read-view之后这个事务也没有进行修改操作,则它的read-view依然保持不变。 即此时的read-view: [100. 200] ,300.。

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

第三次查询时,undo版本链为
Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制_第6张图片
此次查询的事务在之前已经生成了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就越像一个内存数据库。
Mysql(七)MVCC与BufferPool缓存机制 丢失更新 MVCC MVCC的实现原理 隐式字段 undo日志 版本链比对规则 Read View 版 BufferPool缓存机制_第7张图片

  • 将磁盘数据加载到缓存池中
    加载都是一页一页加载的,如加载id为1的记录所在的整页数据
  • undo日志,写入更新数据的旧值,便于回滚。
  • 增删改查,更新内存数据,同时写redo日志
  • 准备提交事务,redo日志写入磁盘
    在数据库的内存里执行了一堆增删改的操作,内存数据是更新了,如果这个时候如果数据库突然崩溃了,没关系,只要从redo log日志文件里读取出来之前做过增删改操作,瞬间就可以重新把这些增删改操作在你的内存里执行一遍,恢复出来之前做过的增删改操作。
  • 准备提交事务,binlog日志写入磁盘
  • 写入commit标记到热动日志文件中,提交事务完成。
  • 随机写入磁盘,还是一页一页写入。

你可能感兴趣的:(mysql,mysql,缓存,数据库)