TopN 怎样才能跑得快?

 

计算 TopN 的 SQL 语句,描述出来的算法是把数据大排序后取出前 N 名。有些数据库优化做的比较好,全集 TopN 没有做大排序,性能尚可。但对于分组后计算组内 TopN 这种更复杂的情况,用 SQL 描述起来都很难,数据库优化就更难了,通常都无法避免排序动作。而大排序的成本很高,如果内存装不下还会涉及多次外存数据倒换的问题,性能会严重下降。大排序也不容易并行,数据库计算 TopN 对并行利用程度也不高。

 

如果能避免大排序,TopN 计算性能就能大幅提升。我们可以保持一个大小为 N 的小集合,遍历数据时,将已经计算过的数据前 N 名保存在这个小集合中,取到的新数据如果比当前的第 N 名还大,则插入进去并丢掉现在的第 N 名,如果比当前的第 N 名要小,则不做动作。这样做,只要对数据遍历一次,不需要大排序。N 一般不会太大,结果集可以放在内存中,不会出现内外存数据交换。分段并行也比较容易,只要每段都算出前 N 名,再对这些前 N 名的并集计算最终的前 N 名即可,可以充分利用并行计算提高性能。

这个新算法的本质是将 TopN 这种返回一个小结果集的情况也看成是聚合运算。这样,分组求组内 TopN 问题,就和分组求和、计数一样了,很容易实现,同样避免了排序计算能够保证性能。

 

但是,SQL 没有显式的集合数据类型,无法描述这个算法。

 

开源的集算器 SPL 支持上述新算法,可以大幅提高 TopN 的计算性能。

SPL 实现全集 top5 的代码样例如下:

=file("T.ctx").open().cursor@m(x).total(top(-5,x), top(5,x))

@m 表示多线程并行,top(-5,x) 计算出 x 最大的前 5 名,top(5,x) 是 x 最小的前 5 名。

 

SPL 实现组内 top3 的代码样例如下:

=file("T.ctx").open().cursor@m(x,y).groups(y;top(-3,x), top(3,x))

按照 y 分组,计算组内 x 最大、最小的前 3 名。

 

实测表明,在同等硬件环境下,用 SPL 实现的全集 TopN 和 Oracle 的 SQL 性能相当,分组 TopN 则比 Oracle 快了 9 倍以上!说明 Oracle 对全集 TopN 做了优化,而分组 TopN 时优化器无法识别出来,只能使用排序。

 

更详细的实现方法和对比测试在这里:性能优化技巧:TopN