Author: 原宇
之前有篇文章《MySQL 深潜 - 统计信息采集》介绍过MySQL索引统计信息的采样过程,这篇文章主要关注的是索引统计信息cardinality的采集以及计算过程,优化器对该统计信息的使用需要满足索引前缀的规则,通俗地讲就是查询中的条件需要满足部分或全部匹配某个索引上的索引列,而且必须是自左向右的匹配顺序,这也就意味着只有索引列才能利用上该统计信息。
对于非索引列或者无法使用索引统计信息的列,MySQL是通过直方图来采集统计信息的。
直方图是MySQL 8.0版本的一个新特性,通过统计采样列的值的频率来得出该列的数据分布情况,这有助于优化器评估一个字段的选择率,从而选择更优的执行计划。
MySQL目前只支持等高直方图,可以理解成会将指定列的值划分到多个区间,每个区间内值的总频率是一样的。
直方图的使用方式比较简单,示例如下所示。
假设有一张表test,通过如下SQL为表test的c列创建直方图。
analyze table test update histogram on c with 10 buckets;
如果不指定buckets数量,默认是创建100个bucket,每个bucket代表一个数据分布区间。
创建好的直方图可以通过information_schema中的视图来查看。
可以看到,test表的c列值被划分到10个buckets,每个buckets中包含4个值,分别表示这个区间的左边界、右边界、区间中值的累计出现频率以及区间中不同值的个数。
前面基于用户角度简单介绍了直方图的使用方式,本文主要介绍MySQL官方直方图采样方式的变化。
无论是索引统计信息还是直方图统计信息,都面临着同样的问题,就是需要在采样率和准确度之间做一个衡量,因为对于大表,对全表数据进行采样是非常耗时的。所以最初的直方图采样会通过概率计算的方式来随机过滤掉部分数据,既可以保证采样的数据均匀分区,又可以达到一个特定的采样率。
我们通常会将MySQL内核代码大致划分为server层和引擎层,最初这个版本的直方图采样以及数据处理都是通过server层代码来实现的,整个过程是这样的。
从这个采样过程,我们可以很明显的发现,虽然采样按照给定的采样率在进行,可以保证内存使用不超出histogram_generation_max_mem_size的限制,但是表中所有数据都被访问过,对于大表来说,数据读取的开销远大于数据处理的开销,磁盘的IO并没有减少,采集的开销几乎没有什么节省。
于是,官方在8.0.19版本引进了一种新的采样方式,可以让引擎层更多的参与进来,直接从引擎层过滤采样数据,避免全表扫描,参见WL#8777。
目前只有InnoDB引擎实现了该采样方式,所以以下算法都是基于聚簇索引来实现。首先我们要知道,InnoDB的聚簇索引通常使用的是Btree,Btree索引是树状组织结构,每个节点对应着一个page,叶子节点(leaf page)保存着表中的数据,一个page中保存着一定数量的行记录,叶子节点是level 0,非叶子节点保存的是索引记录,level大于0,根节点的level取决于树的高度(height-1)。
算法思想:
新的采样算法只是利用WL#11720引入的并行扫描框架来分片以及遍历level 1的page,这个不是本文的重点,后面可以单独写一个文章来介绍这个框架。
WL#8777引入的采样方式依旧是分为三个阶段:
可以看到,整个过程中虽然使用了并行扫描的框架,但是实际上只有一个worker线程在扫描数据,我理解目前的实现是可以最大程度上复用现有代码的,主线程的处理流程没有变化,同样是调用引擎的接口来获取一条数据然后处理,引擎内部的行为发生了变化,但是改动也不算多,并行扫描的使用只需要增加一个回调函数,然后多了一个生产者-消费者的处理关系。
WL#8777这个优化的效果还是很明显的,毕竟原来无论采样率是多少都需要做全表扫描,现在可以减少很多leaf page的扫描。但是经过测试可以发现,对一张大表(TPCH 100G的lineitem表)创建直方图,在采样率低的时候,性能提升十分显著,采样率比较高的时候,性能反而不如原先的采样方式,这里的性能瓶颈在于主线程和worker线程之间相互等待的开销,采样数据越多这个开销相对整体开销的占比越高,所以才会有采样率越高性能越差的现象。不过通常使用直方图时采样率不会很高,所以整体来说是有优化的。
还有一个点比较有趣,之前采样过滤数据的粒度是行记录,现在的采样过滤数据的粒度是page,这一定程度上会导致样本的随机性变差,但是现在采样效率变高,可以通过增加采样率来弥补这一不足。
从代码中可以看到很多可以优化的点: