MySQL源码学习(六):线性预读

举报
爱咬人的猫 发表于 2018/03/10 19:37:45 2018/03/10
【摘要】 InnoDB为了尽可能的让用户经常读取的数据都放在内存中,以减少磁盘的IO次数,提高读性能,加入了预读特性。这个特性会将用户很有可能使用到的数据预先加载到buffer pool中,当用户使用到这个数据时,就不必再从磁盘上读入,从而提升了数据的读取性能。然而一般数据库存储的数据量都会远远大于内存,innodb不可能全部都加载到内存中,对加载数据的选择,决定了预读是否能够有效提升整体性能。

      InnoDB为了尽可能的让用户经常读取的数据都放在内存中,以减少磁盘的IO次数,提高读性能,加入了预读特性。这个特性会将用户很有可能使用到的数据预先加载到buffer pool中,当用户使用到这个数据时,就不必再从磁盘上读入,从而提升了数据的读取性能。

然而一般数据库存储的数据量都会远远大于内存,innodb不可能全部都加载到内存中,对加载数据的选择,决定了预读是否能够有效提升整体性能。

预读的模式

Innodb一共提供了两种预读模式:线性预读和随机预读。两种模式使用不同的算法来预测用户可能用到的数据,并把这些数据异步地从磁盘中加载到buffer pool中,以备使用。

·         线性预读

线性预读是以extend为单位的。通过判断当前的extend中的数据是否是连续访问的,并以此来预测是否有必要把下一个extend提前从磁盘文件中读取出来,加载到buffer pool中。本文主要分析线性预读的使用和原理。

·         随机预读

随机预读针对的是当前extend,通过判断当前extend中的page被读取的次数,来预测是否有必要把当前extend的数据加载到buffer pool中。在MySQL 5.5之后基本已经废弃了随机预读,因此后面的版本随机预读默认是关闭的。

预读的使用

MySQL5.5之后的版本安装好以后默认使用的是线性预读模式。因此无需额外配置就能使用。

与线性预读相关的配置参数,是innodb_read_ahead_threshold,这个参数表示:当一个extend中,大于等于innodb_read_ahead_thresholdpage是被连续访问的时候,就预先加载下一个extend的数据。默认值是56,最大值64,最小值0。如果设置为0,则关闭线性预读。(除非显式打开随机预读,否则如果关闭了线性预读,也不会自动打开随机预读,此时整个预读功能就关闭了)。

线性预读的原理

从上面的说明中,大家可能大概了解了线性预读的使用,然而对于像笔者一样的初学者来说,很多概念其实并没有真正了解其中的含义,因此也就不能指导我们更好的使用预读功能。比如:什么叫做“数据是被连续访问的”,为什么innodb_read_ahead_threshold的最大值是64等等,下面通过对线性预读的源码,来看这个深入理解功能是如何运作的。

线性预读入口函数在:storage/innobase/buf目录下的buf0rea.cc文件中,函数名:buf_read_ahead_linear

·         Extend的边界

我们知道innodb对数据的组织,是:tablespace -> segment -> extend -> page -> row这样的组织顺序。1extend的大小固定为1M1page的默认大小是16K,那么1extend最大可以容纳64Page,并且extend中的page一定是连续存储的。这也就是为什么innodb_read_ahead_threshold的最大值是64,同时也可以看到,对数据连续访问的判断,也仅限制在一个extend中,不会跨越extend.

那么代码中如何区分extend的呢(此处吐槽一下MySQL源代码,如果不事先了解这些概念就,会发现代码中连extend这个关键字都没有)

请看图1532534行就是mysql获取当前extend的边界。

image.png

1 找到当前extend

image.png

2 gdb调试结果

通过图2GDB调试中可以看到,其中buf_read_ahead_linear_area64(因为page大小是可以更改的,因此并不是一个常量,而是计算出来的)。Page number201,通过532534两行简单的就计算出extend的上下边界。上图page 201所在的extend,就是由Page 192Page 255(这里要减1,一共64个)这些Page组成的(这里居然不封装为一个函数。。。)。

同时,从537行的判断可以知道,如果page不是extend的上边界或者下边界,是不执行预读的。也就是说,只有读取到了extend的上、下边界page,才有可能触发预读。

·         预读不会跨域表

如图3代码所示,innodb会获取page所在tablespace,并且判断extend是否跨越了tablespace561行,extend上限page大于space_size),如果跨越,则不会执行预读。


image.png

3 tablespace的判断

·         数据被连续访问的意义

如图4所示,这里就是innodb判断数据是否是被连续访问的算法。

image.png

4 判断连续访问的算法

590行,这里就是innodb_read_ahead_threshold产生作用的地方,如图5和图6所示:

image.png

5 innodb_read_ahead_threshold设置为2

image.png

6 serv_read_ahead_threshold的值为2

innodb_read_ahead_threshold表示了只要2page是被连续访问的,就进行预读,那么此处就是计算出能够允许(64 - 2=62page是没有被连续访问的。另外与BUF_READ_AHEAD_AREA(buf_pool)取最小值的原因是page可能不是16K,那么每个extendpage数量就一定是64了。同时,从这个逻辑也能分析出,如果page不是1K,那么innodb_read_ahead_threshold就没有作用了。

接下来先介绍613614行的buf_page_is_accessed函数,从图7中可以看到,所谓的顺序依据,实际上是pageaccess time,也就是上一次访问时间。

image.png

7 buf_page_is_accessed函数

再看582~586行:lowextend的第一个page位置,如果pageextend的第一个page,那么顺序就是递减(-1),如果是最后一个,那么就是递增(1)。

那么,595~630行的的循环逻辑相信任何一个编程者都能理解:

如果当前pageextend的第一个,那么就看后面的63page中,相邻page访问时间递减的个数,是否≥innodb_read_ahead_threshold个。如果是,则认为可以执行线性预读,加载上一个extend。否则不进行预读。

反之如果当前pageextend的最后一个,那么久看前面的63page中,相邻page访问时间递增的个数,是否≥innodb_read_ahead_threshold个。

 

小结:

线性预读其背后的逻辑,实际上就是:如果我们在读第一份数据的时候发现,之前的数据读取顺序是从后向前读的,那么我们就把前一个区域的数据都加载进来。如果在读最后一份数据的时候发现,之前的数据读取顺序是从前向后读的,那么我们就把后面一个区域的数据加载进来。而innodb_read_ahead_threshold,就是我们认为满足这个顺序的程度。

通过以上的解读,我们也发现,只有在对一个表进行频繁的的顺序、或者逆序查询时,预读才有较好的效果。在随机读取的情况下,线性预读并不能产生太大作用。

 


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。