前言:最近在阅读Innodb IO相关部分的源代码。在阅读之前一直有个疑问,show global status 中有两个指标innodb_data_reads 和 innodb_data_read。两个计数器仅差一个字母,他们之间的含义到底有何差别呢?本文将通过解析这两个参数的含义,分析Innodb对于磁盘IO相关的一些知识
首先我们来看下MySQL官方文档里对于这两个参数的解释:
The amount of data read since the server was started.
The total number of data reads.
文档对于这两个参数的解释非常暧昧。这里的读是指什么?是从哪里读?磁盘还是内存?甚至连计数器的单位也不知道。
详细解析:
接下来我们从源代码来解析这两个参数。在src/Srv0srv.c中存在对这两个参数和代码变量的映射关系的定义代码,如下:
接着让我们来看下这两个代码变量在哪里进行了累加操作。
os_n_file_reads++;
随后,代码即调用os_aio_array_reserve_slot将IO请求推入 simulated array,再根据wake_later标志位决定是否调用
回答这个问题,我们就需要对于innodb的simulated-aio和read-ahead算法有一定理解了。
进入到simulated的aio slot array的请求会有两种,一种是通过buf_read_page 过来的普通页的读请求,一种是通过buf_read_ahead_random/linear 过来的预读请求。从innodb的实现来说普通数据页的请求是需要立即返回响应的,所以是同步(sync)IO。而对于预读这样数据并非SQL所需要,仅是用于提升性能的页读取,这样的IO完全是可以异步的。这两个差异也是导致simulated aio出现的原因。把IO请求推入slot array后,数据页同步请求立即通知worker thread去os做同步IO。而预读IO请求会不断的推入slot array直到一次预读所需要的page全部推入slot中,然后再会通知worker thread。此外在worker thread中,也会判断一个segment的io请求是否相邻连续,如果连续则把这些请求合并后,作为一次OS IO发到下层存储中。
明白了这些也就不难理解为什么计数器要分开在不同的函数中计数了。如果累加都放在 _fil_io中进行,那么 innodb_data_read = 16K * innodb_data_reads (这里假设page没有做压缩)。然而在有了异步IO合并这个操作后,实际的innodb_data_reads会少于_fil_io中获得的计数次数。所以,通过 innodb_data_read / innodb_data_reads得到的比值也可以推断出一个MySQL实例中顺序IO或者可顺序预读的IO比例。
我们在production环境的服务器上做一个验证:
服务器A:在线用户访问的数据库,猜测随机IO较多
SHOW GLOBAL STATUS LIKE '%innodb_data_read%';
Innodb_data_read 46384854470656
Innodb_data_reads 2812101578
每次IO平均读取字节数=16494
服务器B:历史数据统计的数据库,数据内容和服务器A完全一样,猜测顺序IO较多
SHOW GLOBAL STATUS LIKE '%innodb_data_read%';
Innodb_data_read 54835669766144
Innodb_data_reads 2604758776
每次IO平均读取字节数=21052
可见顺序IO较多的MySQL的单次IO读取字节数确实要多于一个page的大小,说明IO合并效果明显。
而随机IO较多的MySQL的单词IO读取字节数几乎和一个page大小一致,即16K
最后我们再总结下一些结论:
参考资料:
1. InnoDB异步IO(AIO)实现详解 http://hedengcheng.com/?p=98