## 背景 之前的月报中我们比较了InnoDB linear read-ahead和Oracle的multiblock read,两个的性能有所差别,具体可以参考[月报详情](http://10.101.233.47:4000/monthly/2015/05/04/ "InnoDB 预读 VS Oracle 多块读")。 这两种方式之所以带来了更高的吞吐量,都基于数据存储的连续性的假设,比如MySQL使用自增字段作为pk的InnoDB索引表,或者是Oracle使用默认的堆表,但当这样的假设条件不成立的时候,怎么办? ## 场景 考虑下面的一个场景,如下图所示: ![InnoDB B-Tree结构](https://box.kancloud.cn/2015-09-24_56039b867b25a.png "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备份开启了此特性,备份速度有非常大的提升。