MySQL源码学习(六):线性预读
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_threshold个page是被连续访问的时候,就预先加载下一个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这样的组织顺序。1个extend的大小固定为1M,1个page的默认大小是16K,那么1个extend最大可以容纳64个Page,并且extend中的page一定是连续存储的。这也就是为什么innodb_read_ahead_threshold的最大值是64,同时也可以看到,对数据连续访问的判断,也仅限制在一个extend中,不会跨越extend.
那么代码中如何区分extend的呢(此处吐槽一下MySQL源代码,如果不事先了解这些概念就,会发现代码中连extend这个关键字都没有)
请看图1,532和534行就是mysql获取当前extend的边界。
图1 找到当前extend
图2 gdb调试结果
通过图2的GDB调试中可以看到,其中buf_read_ahead_linear_area为64(因为page大小是可以更改的,因此并不是一个常量,而是计算出来的)。Page number是201,通过532和534两行简单的就计算出extend的上下边界。上图page 201所在的extend,就是由Page 192到Page 255(这里要减1,一共64个)这些Page组成的(这里居然不封装为一个函数。。。)。
同时,从537行的判断可以知道,如果page不是extend的上边界或者下边界,是不执行预读的。也就是说,只有读取到了extend的上、下边界page,才有可能触发预读。
· 预读不会跨域表
如图3代码所示,innodb会获取page所在tablespace,并且判断extend是否跨越了tablespace(561行,extend上限page大于space_size),如果跨越,则不会执行预读。
图3 对tablespace的判断
· 数据被连续访问的意义
如图4所示,这里就是innodb判断数据是否是被连续访问的算法。
图4 判断连续访问的算法
590行,这里就是innodb_read_ahead_threshold产生作用的地方,如图5和图6所示:
图5 将innodb_read_ahead_threshold设置为2
图6 serv_read_ahead_threshold的值为2
innodb_read_ahead_threshold表示了只要2个page是被连续访问的,就进行预读,那么此处就是计算出能够允许(64 - 2)=62个page是没有被连续访问的。另外与BUF_READ_AHEAD_AREA(buf_pool)取最小值的原因是page可能不是16K,那么每个extend的page数量就一定是64了。同时,从这个逻辑也能分析出,如果page不是1K,那么innodb_read_ahead_threshold就没有作用了。
接下来先介绍613和614行的buf_page_is_accessed函数,从图7中可以看到,所谓的顺序依据,实际上是page的access time,也就是上一次访问时间。
图7 buf_page_is_accessed函数
再看582~586行:low是extend的第一个page位置,如果page是extend的第一个page,那么顺序就是递减(-1),如果是最后一个,那么就是递增(1)。
那么,595~630行的的循环逻辑相信任何一个编程者都能理解:
如果当前page是extend的第一个,那么就看后面的63个page中,相邻page访问时间递减的个数,是否≥innodb_read_ahead_threshold个。如果是,则认为可以执行线性预读,加载上一个extend。否则不进行预读。
反之如果当前page是extend的最后一个,那么久看前面的63个page中,相邻page访问时间递增的个数,是否≥innodb_read_ahead_threshold个。
小结:
线性预读其背后的逻辑,实际上就是:如果我们在读第一份数据的时候发现,之前的数据读取顺序是从后向前读的,那么我们就把前一个区域的数据都加载进来。如果在读最后一份数据的时候发现,之前的数据读取顺序是从前向后读的,那么我们就把后面一个区域的数据加载进来。而innodb_read_ahead_threshold,就是我们认为满足这个顺序的程度。
通过以上的解读,我们也发现,只有在对一个表进行频繁的的顺序、或者逆序查询时,预读才有较好的效果。在随机读取的情况下,线性预读并不能产生太大作用。
- 点赞
- 收藏
- 关注作者
评论(0)