数据库内核月报 - 2015 / 07

MySQL · 引擎特性 · MySQL logical read-ahead

背景

之前的月报中我们比较了InnoDB linear read-ahead和Oracle的multiblock read,两个的性能有所差别,具体可以参考月报详情
这两种方式之所以带来了更高的吞吐量,都基于数据存储的连续性的假设,比如MySQL使用自增字段作为pk的InnoDB索引表,或者是Oracle使用默认的堆表,但当这样的假设条件不成立的时候,怎么办?

场景

考虑下面的一个场景,如下图所示:

InnoDB B-Tree结构

这是一个B-Tree结构,典型的InnoDB的索引聚簇表,这样的结构很容易构造,比如使用一个非连续的字段作为索引字段,随机对记录进行插入,这样leaf page链表上的page_no就会产生非连续性,如果进行一次全表扫描,比如 checksum table t,按照正常的升序扫描,leaf page扫描的page_no顺序是3, 4, 5230等等,这样其实是无法使用到InnoDB 的Linear read-ahead,更没有办法合并IO请求。

对于存在时间比较长,变更又比较多的大表,除非我们对于这个表进行重建,否则leaf page的离散性会随着时间的推移,越来越严重。但对于在线应用来说,重建又会产生比较大的运维风险,这里就介绍一种平衡的方法,logical read-ahead。

logical read-ahead

逻辑预读的概念是指,根据branch节点来预读leaf节点。

逻辑预读使用两个扫描路径:
1. 一个cursor定位到leaf page,然后根据leaf page之间的双链表,moves_up进行扫描数据;
2. 另一个cursor定位到branch节点,因为InnoDB B-Tree结构的每一层都由双向链表进行连接,然后这个cursor就沿着branch节点进行扫描,保存扫描到的page_no,然后使用异步IO,发起这些leaf page的预读取。

代码实现

MySQL 5.6版本上的实现方式:
1. 在row_search_for_mysql进行moves_up的过程中进行logical read-ahead;
2. branch节点扫描的cursor保存到trx结构中,生命周期到一个sql语句结束;
3. branch cursor扫描用户可配置的page count,临时保存到数组中,对page_no进行排序;
4. 使用libaio发起异步IO读取,完成logical read-ahead。

logical read-ahead很好的提升了离散存储数据的吞吐能力,Facebook在他们的MySQL实例的逻辑备份过程中,对于大表的dump备份开启了此特性,备份速度有非常大的提升。