4

InnoDB Buffer Pool改进LRU页面置换 - 邴越

 1 year ago
source link: https://www.cnblogs.com/binyue/p/17273600.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

由于硬盘和内存的造价差异,一台主机实例的硬盘容量通常会远超于内存容量。对于数据库等应用而言,为了保证更快的查询效率,通常会将使用过的数据放在内存中进行加速读取。

数据页与索引页的LRU

数据页和索引页的目的在于缓存一部分的表数据和索引数据,其数据总量通常会超过缓冲池大小,所以缓冲池中应只缓冲那些经常使用的热点数据。InnoDB内存管理使用的是最近最少使用(Least Recently Used, LRU)算法。来淘汰最久未使用的数据

在一般的LRU算法中,当链表中的某一个数据被读取时,将会将其放置于队首。当新增数据且链表已达最大数量时,将链表尾部的数据移除,并将新增的数据置于链表首部。

524341-20230330172629398-1107767340.png

InnoDB的LRU并没有使用传统的双端链表,而是做了改进,这里有两个问题:

  • 缓冲池污染

优化预读失效

由于预读(Read-Ahead),提前把页放入了缓冲池,但最终 MySQL 并没有从页中读取数据,称为预读失效。

Read-Ahead机制

Read-Ahead用于异步预取buffer pool中的多个page的一个预测行为。

InnoDB使用两种提前预读Read-Ahead算法来提高I/O性能。

  • Linear read-ahead 线性预读

如果一个extent中的被顺序读取的page超过或者等于   innodb_read_ahead_threshold  参数变量时,Innodb将会异步的将下一个extent读取到buffer pool中,innodb_read_ahead_threshold可以设置为0-64的任何值(注:innodb中每个extent就只有64个page),默认为56。值越大,访问模式检查就越严格。

  • Random read-ahead 随机预读

如果当同一个extent中连续的13个page在buffer pool中发现时,Innodb会将该extent中的剩余page读到buffer pool中。控制参数  innodb_random_read_ahead  默认没有开启。

如何对预读失效进行优化?

要优化预读失效,思路是:

  • 让预读失败的页,停留在缓冲池LRU里的时间尽可能短
  • 让真正被读取的页,才挪到缓冲池LRU的头部

InnoDB 的具体解决方法

524341-20230330172617080-298774335.png

由上图可以看出 InnoDB 将 LRU List 分为两部分,默认前 5/8 为 New Sublist(新生代)用于存储经常被使用的热点数据页,后 3/8 为 Old Sublist(老生代),新读入的数据页默认被放到 Old Sublist 中,只有满足一定条件后,才会被移入 New Sublist。

新生代和老生代代比例在 MySQL 中通过参数 innodb_old_blocks_pct 控制,值的范围是5到95.默认值是37(即池的3/8)。

  • 如果数据页真正被读取(预读成功),才会加入到新生代的头部
  • 如果数据页没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池

举个例子,整个缓冲池如图

524341-20230330172608238-1023855734.png

假如有一个页号为 50 的数据页页被预读加入缓冲池:

(a). 页号为50 的数据页只会从老生代头部插入,老生代尾部(也是整体尾部)的页会被淘汰掉,即 8 号数据页被淘汰。

524341-20230330172600564-1230183431.png

(b). 假如页号为50 的数据页不被真正读取,即预读失败,它将比新生代的数据更早淘汰出缓冲池

(c). 假如 50 这一页立刻被读取到,例如SQL访问了页内的行row数据。它会被立刻加入到新生代的头部,同时新生代的页会被挤到老生代,此时并不会有页面被真正淘汰

524341-20230330172552169-1125202629.png

改进版缓冲池LRU能够很好的解决“预读失败”的问题。但仍然无法解决缓冲池被污染但问题。

缓冲池污染

当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL 性能急剧下降,这种情况叫缓冲池污染。

缓冲池加入了一个“老生代停留时间窗口”的机制:

(a). 假设T=老生代停留时间窗口

(b). 插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部

(c). 只有满足“被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部

假如批量数据扫描,有91、92、93、94、95、96、97、98、99等页面将要依次被访问

524341-20230330172542511-422933263.png

如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会置换出大量热数据。

524341-20230330172437286-568309088.png

加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。

524341-20230330172427439-2127498798.png

只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。

524341-20230330172419100-639002054.png

老生代的停留时间由参数 innodb_old_blocks_time 控制,单位为毫秒,默认是1000

  1. 缓冲池(buffer pool)是一种常见的降低磁盘访问的机制
  2. InnoDB的缓冲池以数据页(page)为单位缓存数据
  3. InnoDB 对普通 LRU 进行了优化,
  • 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题。
  • 同时采用老生代停留时间窗口机制,当数据页被访问且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK