Author: 郑博(映尧)
ordering index选错是线上经常遇到的一种慢查。典型的例子是:
背景:time 与 id 有很强的关联
例一: select * from t where time > (CURDATE() - INTERVAL 7 DAY) order by id limit 2;
例二: select * from t where time > (CURDATE() - INTERVAL 7 DAY) order by id desc limit 2;
对于例一,如果选中了ordering index,那么几乎要访问完整张表的数据,才能找到最近七天的数据。使用idx_time作range访问是最佳的。
对于例二,使用ordering index倒序扫描,能够立即找到满足条件的数据。但是使用time的range条件就要访问更多的数据。使用idx_id的ordering访问是最佳的。
慢查出现的原因在于ordering index访问代价的计算公式相对粗糙,对于这里的例1和例2,ordering index的实际访问耗时天差地别,但其估算的ordering index访问代价相近。
mysql原生的ordering index代价计算有一个基本的前提假设:符合where条件的数据行在ordering index上均匀分布。
具体的代价公式为:
这里refkey_rows_estimate表示此表的最佳访问方式(除ordering index外)的访问行数。
因此,原始的计算方式存在两点缺陷:
本文针对第一点缺陷作分析,并提出相应的优化方案。
例1和例2的ordering index代价估算错误也完全是由缺陷1导致的。
原始算法认为,在ordering index从左往右扫描的过程当中,符合条件的数据均匀分布。
例1的代价估算图已经指出,问题的根源在于数据分布和数据相关性。由于time列和id列强正相关,最近七天的数据所对应的id一定是很大的,对应于idx_id索引的右边界附近的一小段。从左往右扫描的过程当中,符合条件的数据分布非常不均匀。
如果,我们能够获知ordering index扫描过程中,符合条件的数据分布,那计算精准的扫描行数就是可能的。在correlation=1的情况下,id列和time列完全正相关,符合条件的数据分布完全由上图描述。
当correlation != 1.0,但是接近1.0,某一行数据的id序与time序总体上正相关,局部存在乱序:
id和time之间的correlation不足以直接推演出这两列之间完整的映射关系。但是,如果对time列和id列的数据分布关联作特定的假设,我们可以从correlation反推特定假设下的数据关联,还原数据分布,计算预期扫描行数。这种做法还原的数据关联可能与真实分布不匹配,但总会比无关假设要强很多,且在correlation约等于1的情况下,与真实情况不会偏差太大。
假设: correlation约等于1的情况是,correlation=1情况的随机扰动。
即在correlation 约等于 1 的情况下的数据序为:
这种假设可能会突破排序边界0和n,但是只考虑c较小的情况,这个假设有其合理性。
对于i = 0 ~ k的扫描, 满足
的数据行数:
其数学期望为:
其中
表示a为数学期望,b为方差的高斯分布在c处的概率密度
称为高斯累积分布函数
扫描行数(k)的求解方程为:
由于
都是已知的,d_seq可用相关列的直方图简单推算
c可用correlation推算得到,因此可以求k的数值解,即为扫描行数
每个数据点的序偏离都满足高斯分布gaussian_distribution(0, c)
即,
而correlation的计算公式(斯皮尔曼等级相关系数)为:
可以得到标准差c的近似求解公式:
求解公式为:
公式的左边是,limit所规定的选择率。
公式的右边是,顺序扫描ordering index,扫描到第k行的过程中符合相关条件(correlated_col_i < d)的数据,占全部符合相关条件的数据的比例。
这里只给出单相关条件的修正公式,如果多个列都存在相关性,情况将变得更加复杂。
针对前文给出的两个case,这里给出新旧算法的估算结果,以及真实扫描行。
case1 旧算法估算行数 | case1 新算法估算行数 | case1 真实扫描行数 | case2 旧算法估算行数 | case2 新算法估算行数 | case2 真实扫描行数 | |
---|---|---|---|---|---|---|
correlation = 0.999 | 107 | 9940 | 9892 | 107 | 2 | 2 |
correlation = 0.95 | 38 | 7390 | 7637 | 38 | 5 | 2 |
correlation = 0.84 | 22 | 4700 | 2936 | 22 | 5 | 3 |
correlation = 0.71 | 15 | 2486 | 1873 | 15 | 5 | 2 |
数据构造逻辑: id列自增,time列在id列的基础上加高斯随机。