Mysql的预读机制

Mysql的InnoDB存储引擎中为了做到减少磁盘IO、提高数据库的读取性能而设计了预读机制,预读机制是通过异步将磁盘上的数据页预先加载到Buffer Pool中来加快后续查询效率。预读机制分成两种方式,一种是线性预读,一种是随机预读。

1、线性预读

为了加快数据的读取,Innodb默认使用线性预加载的机制并且通过参数innodb_read_ahead_threshold用来控制是否将下一个extent(连续的64个页看成一个extent)加载到Buffer Pool中,查询这个参数的值如下所示:

show variables like 'innodb_read_ahead_threshold'

通过查询innodb_read_ahead_threshold参数可以发现,如果顺序加载了某个区域的56%的数据的时候,如下所示:

此时Mysql的线性预读认为用户很有可能会读取下一个区域的数据,于是就预先加载下一个区域的数据,Mysql通过异步的方式将下一个extent中全部的数据加载到缓存页中,如下所示:

线性预读适用于顺序访问数据的场景,当数据在磁盘上是连续存储的并且可以预测未来将要访问的页面时线性预读可以发挥最大的功效。

2、随机预读

随机预读默认是关闭的,可以通过参数innodb_random_read_ahead来控制器开闭的状态,如下是Mysql的随机预读查询状态的信息:

随机预读与顺序预读不同,它只要读取了该区域中连续13个页的数据(不管是否是顺序的读取),那么就会将这个区域中剩余的页数据全部加载进来,如下图所示:

上图中block-1到block-13这几个连续的区域中,无论是按照顺序的将磁盘数据依次加载到缓存页还是随机的将磁盘数据加载到缓存页上,如果已经是连续13个缓存页中加载了磁盘数据,那么Mysql随机预读方式就将当前的extent中其他缓存页中数据页加载进来,如下图所示:

随机预读适用于随机访问数据的场景,当数据在磁盘上不是连续存储的或者无法预测未来将要访问的页面时,选择随机预读会更有效。

3、Buffer Pool中LRU链表

预加载的设计目的是为了加快Mysql的查询速度,假设预先加载的数据后续再次被成功使用到,那么读取的速度就大幅度的提升了;但是如果出现预加载的数据不能再次被使用到,比如Mysql全表扫描,此时会将磁盘中很多的页数据加载到Buffer Pool中,由于全表扫码的数据下次很可能用不上,此时大大的降低了 B uff er P ool 的命中率。

常见的降低 B uff er P ool 的命中率的原因有:加载到 B uff er Pool中的页数据不再被使用到、使用率低的数据页加载到 B uff er Pool导致热点数据页淘汰了。针对这些场景innodb中设计了LRU链表(链表中保存的是控制块的信息),如下所示的一个LRU链表:

其中老生代占用37%的大小,主要用来存储使用频率不高的数据;新生代占63%的大小,主要用来存储热点数据。

当查询的数据的时候,首先通过表空间号+数据页号在hash表中判断数据是否被加载过,如果没有加载过就从磁盘将数据加载到缓存页中,并且将缓存页的控制块信息放在LRU链表的老生代的链表头部,如下图所示将block-8的数据添加到LRU链表老生代上:

这样设计的优点是即使发生了全表扫描或者加载了使用了偏低的数据,这些数据也只会放在老生代中,不会影响到新生代中的热点数据。

当老生代的数据超过1秒还在被使用(innodb_old_blocks_time参数控制数据在老生代停留的时间,默认时间是1秒),那么这个数据通过移动链表的方式放到新生代中。如果没有晋升到新生代的数据则会被淘汰,如下图所示老生代中数据淘汰的过程:

老生代中数据晋升到新生代的时候,Innodb对这块做了优化,并不是把老生代的晋升到新生代的数据直接放在老生代的头部了,而是只有被访问的缓存页位于新区域的1/4之后才会被移动到LRU链表的头部。

总结:

Mysql为了提升查询的效率引入了预读方案,为了保证 B u ff e r Pool中的数据都是使用率高的热点数据,Mysql使用LRU链表来保证 B u ff e r Pool中是高频访问数据。

3