最后更新: 2019年10月28日13:35:41sql
本篇文章属于我的备忘录, 主要内容来自: 极客时间《MySQL实战45讲》的第12讲 - 为何个人MySQL会“抖”一下数据库
WAL 是预写式日志, 关键点在于先写日志再写磁盘.性能
在对数据页进行修改时, 经过将"修改了什么"这个操做记录在日志中, 而没必要立刻将更改内容刷新到磁盘上, 从而将随机写转换为顺序写, 提升了性能.测试
但由此带来的问题是, 内存中的数据页会和磁盘上的数据页内容不一致, 此时将内存中的这种数据页称为 脏页日志
这里的日志指的是Redo Log(重作日志), 这个日志是循环写入的.code
它记录的是在某个数据页上作了什么修改, 这个日志会携带一个LSN, 同时每一个数据页上也会记录一个LSN(日志序列号).内存
这个日志序列号(LSN)能够用于数据页是不是脏页的判断, 好比说 write pos对应的LSN比某个数据页的LSN大, 则这个数据页确定是干净页, 同时当脏页提早刷到磁盘时, 在应用Redo Log能够识别是否刷过并跳过.ci
这里有两个关键位置点:it
当内存数据页和磁盘数据页内容不一致的时候, 将内存页称为"脏页".
内存数据页写入磁盘后, 两边内容一致, 此时称为"干净页".
将内存数据页写入磁盘的这个操做叫作"刷脏页"(flush).io
InnoDB是以缓冲池(Buffer Pool)来管理内存的, 缓冲池中的内存页有3种状态:
因为InnoDB的策略一般是尽可能使用内存, 所以长时间运行的数据库中的内存页基本都是被使用的, 未被使用的内存页不多.
刷脏页的时机:
checkpoint 向前推动时, 须要将推动区间涉及的全部脏页刷新到磁盘.
此时若是是干净页, 则直接拿来复用.
若是是脏页, 则须要先刷新到磁盘(直接写入磁盘, 不用管Redo Log, 后续Redo Log刷脏页时会判断对应数据页是否已刷新到磁盘), 使之成为干净页再拿来使用.
固然平时忙的时候也会尽可能刷脏页.
此时须要将全部脏页刷新到磁盘.
InnoDB须要控制脏页比例来避免Redo Log写满以及单次淘汰过多脏页过多的状况.
这种状况尽可能避免, 所以此时系统就不接受更新, 全部更新语句都会被堵住, 此时更新数为0.
对于敏感业务来讲, 这是不能接受的.
此时须要将 write pos 向前推动, 推动范围内Redo Log涉及的全部脏页都须要flush到磁盘中.
Redo Log设置太小或写太慢的问题: 此时因为Redo Log频繁写满, 会致使频繁触发flush脏页, 影响tps.
这种状况实际上是常态.
当从磁盘读取的数据页在内存中没有内存时, 就须要到缓冲池中申请一个内存页, 这时候根据LRU(最久不使用)就须要淘汰掉一个内存页来使用.
此时淘汰的是脏页, 则须要将脏页刷新到磁盘, 变成干净页后才能复用.
注意, 这个过程 Write Pos 位置是不会向前推动的.
当一个查询要淘汰的脏页数太多, 会致使查询的响应时间明显变长.
InnoDB 控制刷脏页的策略主要参考:
当脏页比例接近或超过参数 innodb_max_dirty_pages_pct
时, 则会全力, 不然按照百分比.
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_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语句的响应时间.
这里有一个案例:
测试在作压力测试时, 刚开始 insert, update 很快, 可是一会就变慢且响应延迟很高.
↑ 出现这种状况大部分是由于 Redo Log 设置过小引发的.
由于此时 Redo Log 写满后须要将 checkpoint 前推, 此时须要刷脏页, 可能还会连坐(innodb_flush_neighbors=1
), 数据库"抖"的频率变高.
其实此时内存的脏页比例可能还很低, 并无充分利用到大内存优点, 此时须要频繁flush, 性能会变差.
同时, 若是Redo Log中存在change buffer, 一样须要作相应的merge操做, 致使 change buffer 发挥不出做用.