MySQL长久化鲜为人知的一面⭐️卡顿景象的本源与对策
2024新年新气象,小菜同学又踏上了求职之路,但求职路艰苦,新年第一次面试又被面试官给问住了
面试官:你有没有遇到过因为长久化,把线程的查问、批改申请卡住的状况?
小菜(得意的笑,还想给我挖坑):长久化时写redo log的,利用写redo log的程序性来晋升性能,防止随机IO,因而不会卡住其余线程的申请的
...
面试官:好,那咱们明天的面试就到这里吧
经验本次面试,小菜同学又重新整理缓冲池、长久化相干的知识点终于搞懂卡顿的本源和对策
文章导图如下:
缓冲池
缓冲池的组成
缓冲池是一块内存区域,用于将磁盘中的页加载到内存,放慢访问速度
当拜访数据页时须要先判断页是否在缓冲池中,如果不在则须要从磁盘加载到缓冲池(内存)中
那如何判断某个页是否存在于缓冲池中呢?难度去遍历吗?(遍历是不可能遍历的,工夫复杂度太高)
理论是通过Key:表空间 + 页号
Value:页 的形式建设散列表,达到O(1)的查找速度
数据页被加载到缓冲池后称为缓存页,每个缓存页对应一个管制块,管制块上记录数据页的相干信息
缓存页和对应的管制块组成chunk,chunk是申请间断空间的根本单位(当这片空间被缓存页、管制块没占满时还会剩下碎片)
为了防止扩容时从新分配内存,还要将数据从旧的空间迁徙到新的空间,应用chunk进行扩容
拜访缓冲池的线程会加锁,如果并发量大且只有一个缓冲池,开销会很大
应用分段锁的思维:将一个缓冲池分为多个实例,每个实例相当于有一把锁(页hash到实例),每个实例存在数个chunk
调整缓冲池参数如下:
- 应用
innodb_buffer_pool_instances
调整缓冲池实例的数量 - 应用
innodb_buffer_pool_chunk_size
设置每个实例中的chunk数量 - 应用
innodb_buffer_pool_size
规定缓冲池大小,并且其值必须是innodb_buffer_pool_instanes
和innodb_buffer_pool_chunk_size
的倍数
链表治理
缓存页有三种状态:
- 闲暇:当还未从磁盘加载数据页时,缓存页是闲暇的
- 已应用页洁净:当从磁盘加载数据页到缓冲池时,对应缓存页被占用,但未在页上进行写操作(页不脏)
- 已应用脏页:当有写操作对页中某些记录进行批改时,页并不会立马写回磁盘(这样开销太大),而是通过写redo log的模式保障长久化(后文再说),这种被批改但未写回磁盘的页称为脏页
应用不同的链表管理控制块(对应缓存页):
- 闲暇链表:治理闲暇缓存页的管制块
- 脏页链表:治理脏页缓存页的管制块
留神:链表管理控制块相当于治理对应的缓存页
缓冲池的容量始终是无限的,当缓冲池满时须要将命中不高的页换出,将须要的页换进缓冲池
因而为了晋升缓存命中率,应用LRU链表(LRU算法)治理缓存页
当一个页被查问时,LRU算法无论页是否存在都会放到链表头,如果链表已满,则将最初一个节点移除
这种场景下,如果进行范畴扫描(页数量多),将会把大量页移除链表,全表扫描场景下状况会更蹩脚,这会大大降低缓存命中率
为了防止以上场景产生,MySQL对LRU算法进行优化:
将链表分为冷(old)热(young)数据区,首次拜访的页只放到old区的头部
应用
innodb_old_blocks_pct
规定old区占比 (默认37%)全表扫描可能屡次拜访同一页,所以在规定工夫内屡次拜访某页,不会把它对应管制块放到young区头部
应用
innodb_old_blocks_time
规定该工夫ms (默认1000ms)- 如果页对应管制块就在young头部左近就不挪动(规定在1/4)
留神:LRU链表中也可能存储脏页
长久化
redo log
在聊脏页刷新前须要先搞懂innodb如何长久化
redo log是Innodb存储引擎用于长久化、奔溃复原的重要日志
前文说过,当数据页遇到写操作变成脏页时须要写入磁盘进行长久化
如果对每一条记录都这么做,遇到一个写操作就写入磁盘,而且写回磁盘时,因为页的无序此时会是随机IO,开销十分大
如果想要存一段时间,等该页的脏记录多了再同时刷盘性价比会高一些,然而如果该期间宕机了,那岂不是会产生数据失落?(因为此时还没刷入磁盘)
为了避免数据失落,在宕机时可能进行数据恢复,应用redo log记录页中批改的数据并以程序写入的形式进行IO(程序IO)
当脏页被真正刷入磁盘后,对应的redo log就没有用了,因而redo log被设计成环形文件,以笼罩的形式进行追加日志
redo log通常以ib\_logfile 0...x命名(开端为0-x)
应用 innodb_log_group_home_dir
查看redo log文件所在位置
应用 innodb_log_files_in_group
设置redo log文件数,多个文件数串联造成环形文件
应用 innodb_log_file_size
规定每个redo log文件大小
通过这两个参数能够设置redo log文件的大小
当数据页变成脏页时,会往redo log buffer(缓冲区)上写redo log
因为每个事务中存在SQL,每个SQL都可能对应多个redo log,在往redo log buffer写redo log时,可能波及到多个事务的redo log交替进行写
在进行redo log的刷盘时,会先将数据写入OS的page cache(write),而后依据参数配置 innodb_flush_log_at_trx_commit
不同机会刷入磁盘(fsync)
默认下参数为1,事务提交时会进行fsync将redo log刷入磁盘
当参数为0时,由后盾线程进行write再fsync,吞吐量最高,宕机时会丢数据
当参数为2时,事务提交时只进行write写到OS的page cache,吞吐量也不错,但OS宕机时也会丢数据
bin log
redo log是Innodb(物理)奔溃复原的日志,MySQL还存在逻辑复原的日志binlog,binlog还用于主从复制
bin log的刷盘与redo log也是相似的,先进行write写到OS的page cache(过程快),再进行fsync刷入磁盘(慢)
能够应用 sync_binlog
管制binlog刷盘机会,相似redo log的 innodb_flush_log_at_trx_commit
默认下参数为1,事务每次提交后进行fsync刷入磁盘
当参数为0时,只write不fsync,由OS接管刷盘工夫,吞吐量大,可能失落数据
当参数为X时,经验X事务提交后进行fsync刷盘
在刷盘的过程中为了保证数据的一致性,在redo log刷盘的同时会对bin log一起刷盘
应用XA事务的两阶段提交:
- redo log prepare (write):redo log从缓冲区写入OS page cache
- bin log write:bin log从缓冲区写入OS page cache
- redo log prepare (fsync):redo log从page cache刷入磁盘
- bin log fsync:bin log从page cache刷入磁盘
- redo log commit:刷盘实现,长久化实现
留神:每个事务的redo log是交替写入buffer的,每次提交事务时能够把其余事务的redo log刷入磁盘(组提交)
解体回复时的判断:
- 如果redo log 是commit(已实现第五步)那么间接复原数据
- 如果redo log 是prepare(未实现第五步),查看binlog是否残缺;如果binlog残缺(已实现第四步:bin log fsync)阐明redo log、bin log都实现刷盘能够复原数据,否则不复原
为啥要设计成这样呢?
如果先写完redo log宕机没写bin log,那么主机会通过redo log复原数据,而从机须要通过binlog复原数据,此时binlog不存在就会导致数据不统一
如果先写完bin log宕机没写redo log,那么主机就无奈通过redo log复原数据,从而导致数据不统一
double write
在长久化的过程中还存在double write两次写
如果你了解redo log长久化的过程,是不是想说:两次写就是先写redo log再写数据页,分两次刷入磁盘
其实不是的,这里的两次写代表着数据页会分为两次写入磁盘,应用redo log复原数据须要基于页的完整性,那在页还未刷入磁盘时如何保障页的完整性呢?
思维与redo log相似,通过先程序写数据页的形式保障\~(程序IO代替随机IO)
checkpoint
将redo log刷入磁盘后,期待后续线程将对应的脏页刷入磁盘后,该redo log就能够被笼罩了
然而如何判断环形redo log可被笼罩呢?
在redo log上记录一些lsn(Log Sequeue Number),lsn是自增的(文件环形达到最大后又从终点开始)
lsn:标识写redo log序列号地位
flushed\_to\_disk\_lsn:标识redo log刷入磁盘序列号地位
checkpoint\_lsn:标识checkpoint推动到序列号的地位(可笼罩的地位)
后盾线程会定期checkpoint推动可笼罩redo log的标记,每次进行checkpoint更新checkpoint\_lsn的地位(更新可笼罩的redo log)
lsn与flushed\_to\_disk\_lsn 之间的redo log是没有刷入磁盘的
flushed\_to\_disk\_lsn与checkpoint\_lsn之间的redo log是刷入磁盘的(然而它们对应的数据页可能有的被刷盘,有的没刷盘)
checkpoint\_lsn前的redo log示意可笼罩的(对应数据页曾经刷盘)
脏页刷新
晓得MySQL的长久化机制后,再来看长久化时为啥会卡顿?
写操作太多,很多页没有刷盘,导致redo log占满,此时触发checkpoint将脏页刷入磁盘,空出可笼罩的redo log
又或者是缓冲池已满,要换进新的页时,会将old区开端的页换出,如果该页是脏页,则又要进行刷盘
除了这种场景外还会有线程定时刷新、敞开前把脏页刷入磁盘等
当产生这种场景时,会暂停用户线程去进行刷盘操作从而造成阻塞(相似于JVM中的GC)
因而咱们应该减低这种场景的产生,能够通过调整参数或降级磁盘等多方面实现
以后参数最好通过测试让DBA去调整,总结一下对应的参数
缓冲池
- 应用
innodb_buffer_pool_instances
调整缓冲池实例的数量 - 应用
innodb_buffer_pool_chunk_size
设置每个实例中的chunk数量 - 应用
innodb_buffer_pool_size
规定缓冲池大小,并且其值必须是innodb_buffer_pool_instanes
和innodb_buffer_pool_chunk_size
的倍数
- 应用
LRU算法
- 应用
innodb_old_blocks_pct
规定old区占比 (默认37%) - 应用
innodb_old_blocks_time
规定该工夫ms (默认1000ms)
- 应用
redo log
- 应用
innodb_log_group_home_dir
查看redo log文件所在位置 - 应用
innodb_log_files_in_group
设置redo log文件数,多个文件数串联造成环形文件 - 应用
innodb_log_file_size
规定每个redo log文件大小
- 应用
bin log、redo log刷盘策略
sync_binlog
innodb_flush_log_at_trx_commit
调整io
- 应用
innodb_io_capacity
调整IO能力(应用磁盘IOPS) - 应用
innodb_flush_neighbore
(是否刷脏页的街坊页到磁盘,默认是,应用SSD能够敞开)
- 应用
总结
本篇文章从MySQL的缓冲池开始,总结Innodb中进行长久化的实现原理
缓冲池由数个实例组成,实例由数个chunk组成,chunk由管制块、缓存页组成,每一个缓存页都有一个对应的管制块(缓冲池 -> 实例 -> chunk -> 管制块、缓存页)
缓存页分为闲暇、已应用页洁净、已应用脏页三种状态,应用闲暇链表、脏页链表、LRU链表对缓存页的管制块进行治理
将LRU链表分为冷热数据区,从磁盘加载的页先放到冷数据区,通过一段时间屡次读取后再放入热数据区头部,如果在短时间内屡次拜访一页则不会放入热数据区(避免范畴、全表扫描导致缓存命中率升高),如果页就在热数据区头部左近则不会挪动到头部(1/4)
应用先写redo log再将脏页刷盘的形式,用程序IO代替随机IO
redo log 记录数据页批改的数据,用于实现物理上的数据恢复,因为redo log对应的页刷盘后,该redo log相当于有效,因而被设计成环形文件(可笼罩)
在生成redo log时,会将redo log写在redo log buffer缓冲池,因为每个事务可能对应多条redo log,redo log在缓冲池中是被交替写入的
redo log在进行刷盘时,会先从缓冲池写入操作系统的文件缓存page cache(write 快),再刷入磁盘(fsync 慢)
binlog 是MySQL逻辑上的数据恢复日志,在redo log进行刷盘时,为了保证数据一致性,bin log与redo log 基于XA协定应用两阶段提交
redo log 复原数据基于页的完整性,double write 先让页程序写到磁盘(保障页的可用),后续脏页再刷入磁盘
checkpoint 将脏页刷入磁盘,更新redo log上的checkpoint lsn(更新redo log 可覆盖范围)
当redo log被写满或缓冲池已满冷数据区开端是脏页的场景,都会去让脏页刷新,导致用户线程阻塞,对于这种场景应该让DBA调整参数,降级IO能力解决
最初(不要白嫖,一键三连求求拉~)
本篇文章被支出专栏 MySQL进阶之路,感兴趣的同学能够继续关注喔
本篇文章笔记以及案例被支出 gitee-StudyJava、 github-StudyJava 感兴趣的同学能够stat下继续关注喔~
有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜
本文由博客一文多发平台 OpenWrite 公布!