MySQL WAL(Write-Ahead Log)机制及脏页刷新

最后更新: 2019年10月28日13:35:41sql

本篇文章属于我的备忘录, 主要内容来自: 极客时间《MySQL实战45讲》的第12讲 - 为何个人MySQL会“抖”一下数据库

WAL(Write-Ahead Loggin)

WAL 是预写式日志, 关键点在于先写日志再写磁盘.性能

在对数据页进行修改时, 经过将"修改了什么"这个操做记录在日志中, 而没必要立刻将更改内容刷新到磁盘上, 从而将随机写转换为顺序写, 提升了性能.测试

但由此带来的问题是, 内存中的数据页会和磁盘上的数据页内容不一致, 此时将内存中的这种数据页称为 脏页日志

Redo Log(重作日志)

这里的日志指的是Redo Log(重作日志), 这个日志是循环写入的.code

它记录的是在某个数据页上作了什么修改, 这个日志会携带一个LSN, 同时每一个数据页上也会记录一个LSN(日志序列号).内存

这个日志序列号(LSN)能够用于数据页是不是脏页的判断, 好比说 write pos对应的LSN比某个数据页的LSN大, 则这个数据页确定是干净页, 同时当脏页提早刷到磁盘时, 在应用Redo Log能够识别是否刷过并跳过.ci

这里有两个关键位置点:it

  • write pos 当前记录的位置, 一边写以便后移.
  • checkpoint 是当前要擦除的位置, 擦除记录前要把记录更新到数据文件.

脏页

当内存数据页和磁盘数据页内容不一致的时候, 将内存页称为"脏页".
内存数据页写入磁盘后, 两边内容一致, 此时称为"干净页".
将内存数据页写入磁盘的这个操做叫作"刷脏页"(flush).io

InnoDB是以缓冲池(Buffer Pool)来管理内存的, 缓冲池中的内存页有3种状态:

  • 未被使用
  • 已被使用, 而且是干净页
  • 已被使用, 而且是脏页

因为InnoDB的策略一般是尽可能使用内存, 所以长时间运行的数据库中的内存页基本都是被使用的, 未被使用的内存页不多.

刷脏页(flush)

时机

刷脏页的时机:

  1. Redo Log写满了, 须要将 checkpoint 向前推动, 以便继续写入日志

    checkpoint 向前推动时, 须要将推动区间涉及的全部脏页刷新到磁盘.

  2. 内存不足, 须要淘汰一些内存页(最久未使用的)给别的数据页使用.

    此时若是是干净页, 则直接拿来复用.

    若是是脏页, 则须要先刷新到磁盘(直接写入磁盘, 不用管Redo Log, 后续Redo Log刷脏页时会判断对应数据页是否已刷新到磁盘), 使之成为干净页再拿来使用.

  3. 数据库系统空闲时

    固然平时忙的时候也会尽可能刷脏页.

  4. 数据库正常关闭

    此时须要将全部脏页刷新到磁盘.

InnoDB须要控制脏页比例来避免Redo Log写满以及单次淘汰过多脏页过多的状况.

Redo Log 写满

这种状况尽可能避免, 所以此时系统就不接受更新, 全部更新语句都会被堵住, 此时更新数为0.

对于敏感业务来讲, 这是不能接受的.

此时须要将 write pos 向前推动, 推动范围内Redo Log涉及的全部脏页都须要flush到磁盘中.

Redo Log设置太小或写太慢的问题: 此时因为Redo Log频繁写满, 会致使频繁触发flush脏页, 影响tps.

内存不足

这种状况实际上是常态.

当从磁盘读取的数据页在内存中没有内存时, 就须要到缓冲池中申请一个内存页, 这时候根据LRU(最久不使用)就须要淘汰掉一个内存页来使用.

此时淘汰的是脏页, 则须要将脏页刷新到磁盘, 变成干净页后才能复用.

注意, 这个过程 Write Pos 位置是不会向前推动的.

当一个查询要淘汰的脏页数太多, 会致使查询的响应时间明显变长.

策略

InnoDB 控制刷脏页的策略主要参考:

  • 脏页比例

    当脏页比例接近或超过参数 innodb_max_dirty_pages_pct 时, 则会全力, 不然按照百分比.

  • redo log 写盘速度

    N = (write pos 位置的日志序号 - checkpoint对应序号), 当N越大, 则刷盘速度越快.

最终刷盘速度取上述二者中最快的.

参数 innodb_io_capacity

InnoDB 有一个关键参数: innodb_io_capacity, 该参数是用于告知InnoDB你的磁盘能力, 该值一般建议设置为磁盘的写IOPS.

该参数在 MySQL 5.5 及后续版本才能够调整.

测试磁盘的IOPS:

fio -filename=/data/tmp/test_randrw -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest
注意, 上面的 -filename 要指定具体的文件名, 千万不要指定分区, 不然会致使分区不可用, 须要从新格式化.

innodb_io_capacity 通常参考 写能力的IOPS

innodb_io_capacity 设置太低致使的性能问题案例:

MySQL写入速度很慢, TPS很低, 可是数据库主机的IO压力并不大.

innodb_io_capacity 设置太小时, InnoDB会认为磁盘性能差, 致使刷脏页很慢, 甚至比脏页生成速度还慢, 就会形成脏页累积, 影响查询和更新性能.

innodb_io_capacity 大小设置:

  • 配置小, 此时因为InnoDB认为你的磁盘性能差, 所以刷脏页频率会更高, 以此来确保内存中的脏页比例较少.
  • 配置大, InnoDB认为磁盘性能好, 所以刷脏页频率会下降, 抖动的频率也会下降.

参数innodb_max_dirty_pages_pct

innodb_max_dirty_pages_pct 指的是脏页比例上限(默认值是75%), 内存中的脏页比例越是接近该值, 则InnoDB刷盘速度会越接近全力.

如何计算内存中的脏页比例:

show global status like 'Innodb_buffer_pool_pages%';

脏页比例 = 100 * Innodb_buffer_pool_pages_dirty / Innodb_buffer_pool_pages_total 的值

参数 innodb_flush_neighbors

当刷脏页时, 若脏页旁边的数据页也是脏页, 则会连带刷新, 注意这个机制是会蔓延的.

innodb_flush_neighbors=1 时开启该机制, 默认是1, 但在 MySQL 8.0 中默认值是 0.

因为机械硬盘时代的IOPS通常只有几百, 该机制能够有效减小不少随机IO, 提升系统性能.

但在固态硬盘时代, 此时IOPS高达几千, 此时IOPS每每不是瓶颈, "只刷本身"能够更快执行完查询操做, 减小SQL语句的响应时间.

若是Redo Log 设置过小

这里有一个案例:

测试在作压力测试时, 刚开始 insert, update 很快, 可是一会就变慢且响应延迟很高.

↑ 出现这种状况大部分是由于 Redo Log 设置过小引发的.

由于此时 Redo Log 写满后须要将 checkpoint 前推, 此时须要刷脏页, 可能还会连坐(innodb_flush_neighbors=1), 数据库"抖"的频率变高.

其实此时内存的脏页比例可能还很低, 并无充分利用到大内存优点, 此时须要频繁flush, 性能会变差.

同时, 若是Redo Log中存在change buffer, 一样须要作相应的merge操做, 致使 change buffer 发挥不出做用.

相关文章
相关标签/搜索