准备

  • MySQL 8.0.25

参数解释

  • innodb_old_blocks_pct: 在 Buffer Pool 的 LRU list 中 old 部分所占的比例.

  • innodb_old_blocks_time: 当一个 Page 距第一次被访问的时间大于等于 innodb_old_blocks_time 时,再次被访问的时候,会被移动到 LRU list 的头部.

LRU list

InnoDB 的 Buffer Pool 使用 LRU 算法管理数据 Page, 为了防止全表扫描或者范围查询造成对 LRU 链表的污染, InnoDB 将 LRU 分为两个部分: young / old :

  • young 区域代表经常访问的数据 Page.

  • old 区域代表不常访问的数据 Page.

innodb-buffer-pool-list

上图显示了 Buffer Pool 的布局.

5/8 的 “young” 区域和 3/8 的 “old” 区域划分是参数 innodb_old_blocks_pct 的默认值 37 决定的,这个参数可以动态调整.

源码分析

LRU 初始化

InnoDB 在启动时针对 Buffer Pool 进行初始化, 完成 Buffer Pool 的初始化后使用 100 * 3 / 8 = 37 来调整 LRU list 的 young 和 old 的区域.

插入 LRU old

当我们需要从从 Buffer Pool 中读取一个 Page, 并且这个 Page 需要从文件中进行读取时buf_page_init_for_read(), 我们会从 Buffer Pool 中申请一个 Free Page, 之后需要插入 LRU 的 old 的头部区域buf_LRU_add_block(), 即 old->head:

1
2
3
4
5
6
7
8
9
10
                             新读取的 Page 插入位置
|
|
|
v
-----------------------------------------------------
| | |
| young | old |
| | |
-----------------------------------------------------

插入 LRU young

LRU 区分了 young 和 old 区域,所以需要适时的将 old 区域的 Page 根据需求移动至 young 区域, 操作过程也比较简单,直接从 LRU 的 old 区域摘除然后插入 young 区域即可buf_page_make_young():

以下是插入 LRU young 区域的时机:

  • btr_search_guess_on_hash():

  • buf_page_optimistic_get():

  • buf_page_get_known_nowait():

  • Buf_fetch::single_page(): 对于通过buf_page_get_gen()且 mode 不是 Page_fetch::SCAN 和 Page_fetch::PEEK_IF_IN_POOL 这两种的都会将 Page 插入 LRU list 的 young 区域.

LRU evict

Buffer Pool 的容量是有限的,为了用户的写入读取能获取 Free Page, Buffer Pool 要不停的从 LRU list 置换 “old” Page: 策略是从 Buffer Pool 的 old list 的尾部扫描合适的 Page 换出.

1
2
3
4
5
6
7
8
9
10
                                           LRU evict 起始位置
|
|
|
v
-----------------------------------------------------
| | |
| young | old |
| | |
-----------------------------------------------------

以下是 LRU evict 数据 Page 的时机:

  • buf_page_io_complete(): 当从 LRU list 刷脏完成后,会将 Page 从 LRU list 中移除.

  • buf_flush_LRU_list_batch(): 扫描 LRU list 时,将满足条件的 Pagebuf_flush_ready_for_replace()换出.

  • buf_flush_single_page_from_LRU(): 当用户需要获取空闲 Page 而 LRU List 暂时没有 Free Page 时, 会选择一个 Page 直接换出buf_flush_ready_for_replace()或者buf_flush_ready_for_flush()刷入磁盘.

总结

当一个 Page 从 disk 读入 Buffer Pool 后, 先插入 old 区域起始位置, 后续的非 scan mode 的读则会调整插入 young 区域. 在 young 区域的 page 假如再次被读到,会通过buf_page_peek_if_young()判断是否接近被 evict, 否则在 young 中是不会调整 page 的顺序的.

推荐文档