暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL:线性预读浅析

MySQL学习 2023-03-06
547

仅代表个人观点,有误请谅解。


线性预读判定条件

线程预读默认是开启的,主要是在进行物理page读取的时候,如果满足一定规则,则采取预读。线性预读触发条件在buf_read_ahead_linear函数中,主要逻辑比较简单,大概有如下一些原则:

  • 必须设置了innodb_read_ahead_threshold参数
  • 当访问到某个extent的边界值比如有如下4个extent
ext1   ext2         ext3      ext4
0-63  64-127 128-191 192-255

如果访问到128这个page或者191这个page,则进行ext3所有page的检查,检查方式为访问ext3中64个page的最后访问时间,分2种方式

  • 如果访问是128这个 low limit,则进行降序检查也就是需要满足如下
page 128的访问时间 > page 129的访问时间 > page 130的访问时间...

这可能是一种反向的全索引扫描。比如DESC 反向索引全扫描

  • 如果访问是191这个 high limit,则进行升序检查也就是需要满足如下
page 128的访问时间 < page 129的访问时间 < page 130的访问时间...

正向全表扫描或者索引扫描使用这种检查,因为page在叶子节点有序。如果检查出来不符合访问顺序的page大于了 64 - innodb_read_ahead_threshold(默认56) = 8 个那么,则不进行线性预读。 这里包含一个关键问题就是一个extent的block是否能够按照索引(或者主键)的顺序进行排列,如果不能满足这个要求这个算法并不能体现很好的全表扫描性能。而如果不是自增的主键那么可能导致数据对于主键来说是乱序的,那么可能相邻的数据分布在不同的extent里面甚至相差甚远,这就导致线性预读不能体现出优势,这个地方可以参考流程:

btr_page_split_and_insert
  ->btr_page_alloc
    ->btr_page_alloc_low
      ->fseg_alloc_free_page_general
        ->fseg_alloc_free_page_low

最后的函数fseg_alloc_free_page_low负责底层block的分配,如果发现extent满了则hint_page的page no是不能分配的,而会分配到有空闲空间的extent里面。

线性预读方式分2种

  • 如果是反向扫描,则直接访问前一个extent

  • 如果是顺序扫描,则直接访问后一个extent

线性预读和异步IO线程

读取方式为循环这64个page,大概如下:

for (i = low; i < high; i++) {buf_read_page_low(i)}

这里的low和high分别对应预读extent的开始和结束page no,也就是进行分别读取每个page,但是使用的方式是异步AIO,最终每次调用io_submit将IO需求发送给操作系统。而异步IO线程则通过调用:

io_handler_thread
->fil_aio_wait
   ->os_aio_handler
      ->os_aio_linux_handler
         ->LinuxAIOHandler::poll
             ->LinuxAIOHandler::collect
                  ->io_getevents

进行io完成情况的收集,然后进行IO完成后调用buf_page_io_complete完成IO操作,比如解除IO fixed状态,

buf_page_set_io_fix(bpage, BUF_IO_NONE);

预读使用异步IO也是可以理解的,当我们再次访问page查看是否在innodb buffer中的时候会发现page已经存在于buffer中也就不需要再次进行物理IO了,而将读取IO的任务交给异步IO来完成,这实际上做到了前台session和后台IO线程同步并发进行操作的原则,对于顺序全表扫描,且数据有续的情况确实提高的了性能。

当我们将异步IO线程加入等待sleep后,发现实际上前台session在查询page的时候会发现这个page还没有完成buffer的读取,也就是发现这个page已经被fixed住了,则进行等待。并且show engine出现大量的pending read。

简单顺序物理IO性能测试

从连续数据的线性预读来看,效果还是比较明显(笔记本虚拟机性能糟糕),连续数据这里是顺序插入的表且主键是自增,表大小1G左右,同时必须设置很小的innodb_buffer 比如50M,这样肯定是装不下这个表的,让全表扫描基本为物理IO。下图:

image.png

这一这里设置参数innodb_read_ahead_threshold=2 是为尽可能的使用线性预读,其实对于这个场景并不需要,且56是默认值控制比较严格,否则可能导致很多无畏的page被读取到buffer。当然我们也可以对线性预读进行strace,发现有很多很多的io_submit这也是异步IO的体现如下:

最后

我们可以通过指标Innodb_buffer_pool_read_ahead 来查看是否有线性预读的情况,这是一个累积值,同时可以通过Innodb_buffer_pool_read_ahead_evicted来查看是否预读的数据没有使用就被剔除了,同样是一个累计值,如果增长过快说明预读没啥用。


文章转载自MySQL学习,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论