vlambda博客
学习文章列表

mysql中缓存的一些用法,change buffer与double write

    以前觉得学习会上瘾,现在觉得写文章也会上瘾,我这个”瘾君子“啊。在这篇文章中对mysql innoDB 缓冲池的一些其他内容的概括,以及我自己在这个过程中的一些思维运转,作为一个努力成长的小小程序猿,大家挑出来的毛病都是我成长路上的动力,都是日后作为总结的宝贵参考坐标。

     MySQL缓冲池作为CPU与数据库之间的缓冲介质,一个提高数据库性能的设计,我们可以看出这又是一个缓存的用法。在innoDB中对一个数据的读取变更我们首先操作的都是这层缓存,然后通过checkpoint机制再刷新回磁盘,这样一条数据就持久化到了磁盘中保证了数据的永久性存储。那么在这个过程中可能会发生哪些问题呢?又该如何去解决呢?

    我们知道内存是一个极其宝贵的资源,那么在某种程度上就意味着我们要尽可能采用性价比最好的方式来利用它,例如在innoDB中我们使用LRU算法来保证热点数据与不常用的数据在内存中存放的策略。我们知道innoDB是基于B+树的数据结构进行存储的,当按照主键进行存储时,其时有顺序递增的插入(只有当主键为autoincrement时),而二级索引与聚簇索引(ps:默认是InnoDB里的主键,主键是聚集存储的)不同,二级索引通常不是唯一的,并且插入二级索引的顺序相对随机。删除和更新可能会影响不在索引树中相邻的二级索引页,这样就会造成大量的物理读(物理读:从磁盘读取数据块到内存的操作叫物理读,当缓存不存在这些数据块的时候就会产生物理读,物理读过大表现为磁盘 I/O 较高)。由此我们引入了change buffer(早期版本中只针对insert buffer,所以也叫插入缓冲)。

    二级索引又叫非聚簇索引,叶子节点存储的是 索引 和 主键 信息,在找到索引后,得到对应的主键,再回到聚集索引中找主键对应的记录(row data)。所以,当使用主键去访问数据时,说明你动脑了。

    我们先看一张图,这是mysql官网对change buffer的解释

    

    从上图我们可以看到有了change buffer后更新删除时先放入change buffer,然后再选择时机进行merge insert buffer的操作,将insert buffer中的记录合并到真正的辅助索引中。下面再配上一个懒人图(实在不想画,从我的书上拍一张吧)


    配上一段官网的解释,英文就是显得逼格高一些啊。

    Merging cached changes at a later time, when affected pages are read into the buffer pool by other operations, avoids substantial random access I/O that would be required to read secondary index pages into the buffer pool from disk.

Periodically, the purge operation that runs when the system is mostly idle, or during a slow shutdown, writes the updated index pages to disk. The purge operation can write disk blocks for a series of index values more efficiently than if each value were written to disk immediately.


    解释一下,当有其他操作将受影响的页面读入缓冲池时,会将change buffer与新读取的进行缓存的更改合并,也就是图中的merge,然后在系统基本闲置或者缓慢关闭期间定期执行执行perge操作,将更新后的索引页磁盘,这与将每个值立即写入磁盘相比,极大的减少了磁盘I/O。

    因为它可以减少磁盘读取和写入,所以更改缓冲区功能对于I / O绑定的工作负载最有价值,例如具有大量DML操作的应用程序(如批量插入)。但是,Change Buffer占用Buffer Pool的一部分,从而减少了可用于缓存数据页的内存。如果工作集几乎适合Buffer Pool,或者您的表具有相对较少的二级索引,则禁用Change Buffer可能很有用。至于如何配置操作,附上官网链接,没有什么比按照官网api进行操作更合适的了。https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html


    一个系统在考虑其性能的同时数据的安全性保证也是必须具备的。有一定项目经验的小伙伴都知道项目的数据库要有数据库备份,冷备、热备、温备等等。由此可能还有一系列的备份架构,比如主从的备份策略,主从之上的快照+主从等等。这些都是为了保证在服务器宕机或者其他错误操作引起的数据丢失问题发生时有可以及时恢复之前可用的数据。那么在innoDB的内部是不是也有这样的策略呢?

    这里就要提到redo log buffer 与double write了。redu log buffer时一块内存区域,存放将要写入redo log文件的数据。redu log buffer会周期性地刷新到磁盘的redo log file中。一个大的redo log buffer允许大事务在提交之前不写入磁盘的redo log文件。因此如果有事务需要cud许多记录时,则可增加redo log buffer来节省磁盘I/O。double write技术的引入时为了提高数据写入的可靠性。这里面有一个问题叫部分写失效(partial page write),我们知道数据库中一个page的大小是16KB,数据库往存储上写数据时式以更小的单位进行的,这就产生了一个问题:当发生数据库宕机时,可能InnoDB存储引擎正在写入某个页到表中,而这个页只写了一部分,比如16KB的页只写了4KB,然后宕机了。其原理时每写入一个page时,先把page写到double write buffer中。如果在写double write buffer发生了意外,但是原page不受影响,下次启动时可以通过redo log进行恢复。当上一步没问题,从double write buffer向数据文件写入的过程中发生了意外呢?也没关系,在重启后MySQL可以从double write buffer找到好的page,再用好的page覆盖坏的page,解决了磁盘上page坏的问题。double write的思想说白了就是备份镜像。

    工作流程如下图


对redu log再多做一些解释,有人这时候可能会有疑问,说MySQL redu log不是已经记录了所有的数据历史记录了吗?

    要弄明白这个问题,首先要了解一下redo log里面记录了什么东西。日志分为物理日志和逻辑日志。物理日志就是直接记录数据、记录被修改页的偏移量,有点就是不依赖原页面的内容,用日志的内容可以直接覆盖到磁盘上面,缺点就是占用的空间太多。逻辑日志的优点比较简洁,而且占用的空间较小,缺点就是需要依赖原page内容,而且会有部分执行和操作一致性的问题(关于操作一致大家认真思考就会发现,在异步或者不同事务中,一个系列的操作上要求的数据一致都会存在数据一致性问题,比如MQ做消息队列时的消息一致,多研究几个以后遇到问题可能脑海里的解决方案就会多几个,这很重要)。

    redu log是物理逻辑的,它将物理日志和逻辑日志相结合,取其利,避其害,从而达到一个更好的状态,具体点就是:物理表示记录的日志针对的是页的修改,为每个页上的操作单独记日志;逻辑表示记录日志的内容是逻辑的,比如物理上来说要修改Page header页头的内容、要修改相邻记录里链表指针等。这些本来是物理操作的范畴,但是InnoDB为了节省日志量,设计为逻辑处理的方式。当redu log应用到磁盘时,在写一个Page到磁盘过程中发生了故障,这时Page Header的记录数可能被加1(表示此Page已经恢复完了),但是业内的逻辑日志发生了故障,这时数据就不一致了。MySQL这时就会用double write去覆盖这个page使得坏page被好page覆盖,以此来解决这类问题。

    还有就是double write在传统机械硬盘中时顺序写入,所以相对于随机写代价还是比较小的。

    double write并不是什么优化或者优势策略,可以理解成是一个被动解决方案。本质是磁盘在写入时没有保证数据页面16KB的一次性原子写,所以采用了一个double write的缓存备份方案。

    这些成熟产品或者架构给我们提供了很好的已有方案在问题解决上的借鉴,对其思想的掌握,知识灵活性的储备,在遇到问题时我们可以在脑海里提供多个解决方案去筛选。