MYSQL事务的底层原理( 六 )


在 Buffer Pool 中会创建多个缓存页 , 默认的缓存页大小和在磁盘上默认的页大小是一样的,都是 16KB 。
那么怎么知道该页在不在 Buffer Pool 中呢?
在查找数据的时候,先通过哈希表中查找 key 是否在哈希表中 , 如果在证明 Buffer Pool 中存在该缓存也信息,如果不存在证明不存该缓存也信息,则通过读取磁盘加载该页信息放到 Buffer Pool 中,哈希表中的 key 是通过表空间号 + 页号作组成的,value 是 Buffer Pool 的缓存页 。
flush 链表的管理
如果修改了 Buffer Pool 中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页也被称为:脏页 。最简单的做法就是每发生一次修改就立即同步到磁盘上对应的页上,但是频繁的往磁盘中写数据会严重的影响程序的性能 。所以每次修改缓存页后,并不着急把修改同步到磁盘上,而是在未来的某个时间进行同步 。但是如果不立即同步到磁盘的话,那之后再同步的时候怎么知道 Buffer Pool 中哪些页是脏页,哪些页从来没被修改过呢?总不能把所有的缓存页都同步到磁盘上吧,如果 Buffer Pool 被设置的很大,那一次性同步会非常慢 。
所以,需要再创建一个存储脏页的链表 , 凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中,因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的,所以也叫 flush 链表 。
刷新脏页到磁盘
后台有专门的线程每隔一段时间负责把脏页刷新到磁盘 , 这样可以不影响用户线程处理正常的请求 。
从 flush 链表中刷新一部分页面到磁盘,后台线程也会定时从 flush 链表中刷新一部分页面到磁盘,刷新的速率取决于当时系统是不是很繁忙 。这种刷新页面的方式被称之为:BUF_FLUSH_LIST 。
redo 日志redo 日志的作用
InnoDB 存储引擎是以页为单位来管理存储空间的 , 增删改查操作其实本质上都是在访问页面,包括:读页面、写页面、创建新页面等操作 。在真正访问页面之前 , 需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问 。但是在事务的时候又强调过一个称之为持久性的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃,这个事务对数据库中所做的更改也不能丢失 。
如果只在内存的 Buffer Pool 中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了,这是所不能忍受的 。那么如何保证这个持久性呢?一个很简单的做法就是在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:

  1. 刷新一个完整的数据页太浪费了;有时候仅仅修改了某个页面中的一个字节,但是在 InnoDB 中是以页为单位来进行磁盘 IO 的,也就是说在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,一个页面默认是 16KB 大?。?恍薷囊桓鲎纸诰鸵?⑿?16KB 的数据到磁盘上显然是太浪费了 。
  2. 随机 IO 刷起来比较慢;一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的 Buffer Pool 中的页面刷新到磁盘时,需要进行很多的随机 IO,随机 IO 比顺序 IO 要慢,尤其对于传统的机械硬盘来说 。
只是想让已经提交了的事务对数据库中数据所做的修改永久生效 , 即使后来系统崩溃,在重启后也能把这种修改恢复出来 。其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好,比方说:某个事务将系统表空间中的第 5 号页面中偏移量为 5000 处的那个字节的值 0 改成 5 只需要记录一下:将第 5 号表空间的 5 号页面的偏移量为 5000 处的值更新为:5 。
这样在事务提交时,把上述内容刷新到磁盘中 , 即使之后系统崩溃了,重启之后只要按照上述内容所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改又可以被恢复出来,也就意味着满足持久性的要求 。因为在系统崩溃重启时需要按照上述内容所记录的步骤重新更新数据页,所以上述内容也被称之为:重做日志 , 即:redo log 。与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比,只将该事务执行过程中产生的 redo log 刷新到磁盘的好处如下:


推荐阅读