一条 SQL 语句,正常快,有时特别慢,难复现,随机持续时间短。
一、SQL 语句为什么变“慢”
第 2 篇文章介绍 WAL 机制。InnoDB 更新时,只做写日志(redo log重做日志)这个磁盘操作。酒店记账粉板,更新内存写完redo log 返回给客户端,更新成功。
掌柜记忆是内存。更新账本,内存里写入磁盘 flush。flush前,赊账总额和账本记录不一致。今天赊账只在粉板。
不一致时,内存页为“脏页”。一致,为“干净页”。
原来欠 10 文,又赊 9 文。
MySQL “抖”瞬间,可能就是刷脏页(flush):将内存页写入磁盘。
二、什么情况会引发数据库 flush?
1、粉板满了,再有赊账,放下手里活儿,粉板记录擦掉一些,继续记。擦掉前记账本。redo log 写满,停止更新,checkpoint 往前推,redo log留空继续写。
checkpoint 不是随便往前修改位置就可以。从 CP 推到 CP’,两点之间日志,所有脏页 flush 磁盘。write pos 到 CP’可再写。
2.生意太好记不住了,找账本把孔乙己这笔账先加进去。1)写binlog
内存不足,需新内存页,内存不够用,淘汰数据页。如果淘汰“脏页”,先将脏页写磁盘。2)写磁盘,空出内存
内存直接淘汰掉,下次从磁盘读入数据页,拿 redo log 出来应用不就行?性能考虑。如果刷脏页一定写盘,保证数据页两种状态:
(1)内存里存在,直接返回;
(2)内存没有数据,文件上肯定是正确,读入内存后返回(效率高)。
3.生意不忙,更新账本。MySQL“空闲”刷“脏页”。
4.年底结账。MySQL 正常关闭脏页flush 到磁盘上,下次启动直接从磁盘上读,快。
四种场景对性能影响
三、四不会太关注“性能”。
(1)redo log 满, InnoDB 尽量避免。系统不能更新,监控上看,更新数跌为 0。
(2)内存不够用,常态。InnoDB 缓冲池(buffer pool)中的内存页有三种状态:
没用; 用了干净页; 用了脏页。
策略:尽量使用内存,长时间运行库,未被使用页很少。
要读入数据页没在内存,到缓冲池申请。从内存中淘汰数据页(最久不用):干净页,释放复用;脏页,刷到磁盘,变干净复用。
刷脏页也会影响性能,控制脏页比例,避免:
1. 淘汰脏页太多,响应时间明显变长;
2. 日志写满,更新堵住,写性能 0。
三、InnoDB 刷脏页的控制策略
告诉 InnoDB 所在主机的 IO 能力知道全力刷脏页速度
innodb_io_capacity 设置成磁盘的IOPS(用于计算机存储设备,如硬盘(HDD)、固态硬盘(SSD)或存储区域网络(SAN))性能测试的量测方式。
通过 fio工具测试磁盘随机读写命令:
fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest
没正确地设置innodb_io_capacity 参数,导致性能问题。MySQL 写慢,TPS 很低,数据库主机的 IO 压力不大:
主机磁盘SSD,innodb_io_capacity= 300。InnoDB 系统能力差,刷脏页比脏生成速度还慢,脏页累积,影响性能。
不能一直全力刷,还需服务用户请求。
3.1控制刷脏页速度,参考因素
刷太慢,1、脏页太多,2、redo log 写满
InnoDB 先单独算出两个数字:
1、根据当前脏页比例 M,算出0 ~100数字,计算数字伪代码:
多关注脏页比例,不要让它经常接近 75%
F1(M)
{
if M>=innodb_max_dirty_pages_pct (脏页比例上限,默认75%)then
return 100;
return 100*M/innodb_max_dirty_pages_pct;
}
2、InnoDB 每次写日志序号,跟 checkpoint 序号差 = N。根据N 算出 0 ~100 数字,公式F2(N)。算法复杂,N 越大,算出值越大。
F1(M) 和 F2(N) 取较大值为 R,innodb_io_capacity 乘以 R% 控制刷脏页的速度。
F1、F2 通过脏页比例和 redo log 写入速度算出来的两个值。
脏页比例:nnodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total 得到的,命令:
mysql> select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty';
select VARIABLE_VALUE into @b from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total';
select @a/@b;
3.2有趣的策略:
查询请求需先 flush 掉脏页,比平时慢。MySQL一个机制,让查询更慢:刷脏页时,旁边是脏页,“邻居”一起刷掉;相邻还是脏页一起刷。
innodb_flush_neighbors控制这个行为,= 1“连坐”机制,= 0 不找邻居(默认)
找“邻居”机械硬盘(随机 IOPS 几百)时代有意义,减少很机 IO。相同逻辑减少随机 IO 性能提升。
SSD 这类 IOPS 高的设备,innodb_flush_neighbors = 0。 IOPS 不是瓶颈,“只刷自己”更快。
小结
WAL 的概念(延续第 2 篇中介绍)
解释这个机制后续需要刷脏页操作和执行时机。
WAL 技术,数据库将随机写转换成顺序写,大大提升数据库性能。
内存脏页问题:脏页会被后台线程自动 flush,也由于数据页淘汰而触发 flush,刷脏页过程占用资源,可能让更新和查询语句响应时间长。介绍了控制刷脏页方法和监控方式。
思考题
内存128GB、innodb_io_capacity = 20000 大规格实例,正常建议将 redo log 设置成 4 个 1GB 的文件。
不慎将 redo log 设置成 1 个 100M 的文件,发生什么情况?为什么?
答:每次事务提交都写 redo log,设置小很快被写满,write pos 一直追 CP。停止所有更新,推进 checkpoint。
现象:磁盘压力很小,数据库出现间歇性性能下跌。
评论1
刷脏页对应redo log位置随机,redo log不同位置刷掉,redo log处理不是很麻烦?(合并间隙,移动位置?)
redo log优势:将磁盘随机转换成顺序写,这样不是redo log里随机读写么?
答:刷脏页过程不用动redo log文件。redo log“重放”时,数据页刷过,会识别跳过。
评论2
redo log保证ACID的D。
1.脏页保存到磁盘,checkpoint往前推
2.redo log记录undo变化,undo log buffer持久化undo log
3.innodb_flush_log_at_trx_commit=0,内存redo log持久化到磁盘
4.redo log记录change buffer改变,要把change buffer purge到idb
连change buffer的优化也没意义了
以及merge change buffer.merge也是脏页,持久化到磁盘
上述都是占用系统I/O,影响DML,操作频繁,'抖'向过冬。
select查询时间相对会更快。脏页变少,不用淘汰,直接用干净页。宕机恢复,速度快,checkpoint很接近LSN,恢复数据页相对较少
评论3
抖动现象,问题:
1)Innodb_buffer_pool_pages_total百万级别,不像人为设置,怎么来?
2)Innodb_buffer_pool_pages_dirty4万多开始flush了,脏页比例75,远达不到的,ssd磁盘,innodb_io_capacity=200,可以高。flush触发条件,内存不够,redo log 满,这个场景是哪种情况呢
回复: 1)是innodb 数据页总,过百万正常,16K一个,Bufree pool size 16G 就是100万
2)io_capacity设太小
评论4
buffer pool里维护脏页列表,假设redo log 的 checkpoint 记录 LSN (日志序列号)= 10,内存中一干净页有修改LSN为12,大于 checkpoint 的LSN,写redo log同时该页也会被标记为脏页记录到脏页列表中,现内存不足,该页被淘汰刷到磁盘,LSN=12从脏页列表中移除,redo log推进checkpoint,到LSN=12log时,为干净页跳过。
评论5
redo log写满:redo log对应脏页flush,释放空间。问题:
1、脏页flush到磁盘上,接将脏页数据覆盖到对应磁盘上数据?是的
2、redo log断电重启,内存丢失,通过redo log数据恢复,redo log怎么释放空间?
重启了就从checkpoint 位置往后扫。 之前刷过盘, 不会重复应用redo log。
评论6
如何判断数据页是否在内存当中?
每页又编号。拿编号去内存看,没有,就去磁盘