大家好,我是 青峯!😉
今天分享的内容是mysql 内存结构中的 Buffer Pool 。
欢迎关注,持续更新中…
进入主题,带着以下问题进一步挖掘Buffer Pool😉:
📌Buffer Pool 独有LRU算法是怎样的?
📌InnoDB 是如何保证 Buffer Pool 落盘的?
📌Buffer Pool 刷盘时机?
💡 Buffer Pool 独有LRU算法是怎样的?🔽
一般的LRU算法,也是用链表实现(链表特点是增删高效且增删时不会有大量元素在内存中移动),把最热的数据放到链头(若数据本身就在链表中,就把其移到链头),其他已有数据往后移动,如果链表满了,淘汰最后多出的元素。
对于mysql来说,上述LRU算法有两个缺点:
预读失效
和
缓存池污染
。
mysql的预读是从磁盘中读取数据页到内存时,顺带把相邻的数据页一并读取。mysql通过两个参数控制预读
innodb_read_ahead_threshold
:默认值56,读取.ibd文件中某区连续的数据页数量超过56,就触发预读机制,把下一个相邻区的数据页读进buffer pool;
innodb_random_read_ahead
:默认值off,如果某区连续13个页在buffer pool中,并且这些数据页频繁被访问,则就触发预读机制,把该区剩余数据页读进buffer pool;
预读有效:数据访问通常遵循集中读写原则,使用一些数据,大概率也会使用附近的数据,这就是局部性原理,说明提前加载是有效的,能有效减少磁盘IO。(实际怎样,没去考究,起码觉得是能起到一定作用的🙄)
预读失效
:指数据页提前被读进缓存池,到真正访问数据时,在缓存池找不到匹配的数据。
怎么解决此问题?
mysql将LRU算法进行改良,把双向链表分成两截(新区和老区),新区占63%,老区占37%(这个比例是默认的,官方测试过才这么分配的,通过
innodb_old_blocks_pct
设置老区占比)。被预读进来的数据页 和 被第一次访问的数据页 先放在老区,等再次访问的时就移到新区链头,其余元素往后移。实际只有新区的后3/4的数据被访问才会移动到链头。
缓存池污染
:指某一次读取大量数据,淘汰掉缓存池里所有的热数据。
怎么解决此问题?
在上面的基础再添加一项设定,老区中的元素想要进入新区,需满足一个条件(在默认1s内再次被访问,这个时间可以通过
innodb_old_blocks_time
修改)就能加入到新区。
mysql对链表的设置
innodb_old_blocks_pct
:默认值37,设置老区占比
innodb_old_blocks_time
:默认值1000,设置经过多少毫秒才加入新区
innodb_buffer_pool_size
:默认值128MB,设置缓冲区大小
综上所述,buffer pool 独有的LRU算法把双向链表按比例分为新老区,并设置时间窗口来控制热数据写入新区。
💡 InnoDB 是如何保证 Buffer Pool 落盘的?🔽
文件系统中文件一页大小是4KB (命令 getconf PAGESIZE 可查),内存一页大小16KB,因此内存要把一页文件刷到磁盘,可能要走4次磁盘IO,这还是非原子性操作。若出现OS崩溃、服务器宕机、断电等情况直接导致页数据不一致,页损坏。
怎么解决此问题?
InnoDB采用 double write 技术,在内存中开辟两块连续1MB大小的区域(double write buffer)分别能存放64页数据。当有数据要刷盘时,
- 先把内存页数据copy到double write buffer中;
- 然后把double write buffer数据分两次fsync刷入磁盘每次1MB,最终生成.dblwr文件,此操作为顺序IO,速度快;
- 最后把double write buffer数据刷入对应的.ibd文件中;
步骤1、2、3 能够避免上述突发情况,保证磁盘页数据不被损坏。
步骤1失败 -》 磁盘页数据正常
步骤2失败,步骤3成功 -》 磁盘页数据正常
步骤2成功,步骤3失败 -》 磁盘页数据通过.dblwr文件恢复,页数据正常
Innodb_doublewrite
:查看是否开启double write
Innodb_dblwr_pages_written
:记录写入double write buffer中页的数量
Innodb_dblwr_writes
:记录double write buffer写操作的次数
Innodb_dblwr_pages_written / Innodb_dblwr_writes = 每次写多少页,一次最多能刷盘64页。可判断写压力是否大
💡 Buffer Pool 刷盘时机?🔽
计算机先把内存 write 到内核缓存空间,等待OS 对内核缓存空间执行 fsync 操作刷入磁盘。这里分了两步,mysql认为完成第一步就当作刷盘成功了 ,只有真正完成第二步才是成功刷盘。当系统崩溃后内核缓存空间数据丢失,不可靠,而每次都 fsync 操作,效率下降,所以可靠性和性能只能权衡。
刷盘时机出现在
- buffer pool 空间不足时(按最少访问原则)需要淘汰一些数据页,其中被淘汰的页可能是脏页(被update过的页)需要更新磁盘;
- redo Log 写满时(留到 change buffer 再详细说);
- mysql空闲时后台线程触发刷盘,在繁忙时也会见缝插针刷盘;
- mysql正常关闭时刷盘;