WHERE 中有很多 IN 判断怎么提速?
WHERE 中的 IN 是要过滤出某字段值包含在给定枚举值集合中的记录,比如:查出某几个城市的客户、某些类型的订单等等。
数据库做 IN 过滤时,要用字段值和值集合的成员作比较计算。若采用顺序查找,要比较 1 到 n 次(n 是值集合大小)。即使在值集合有序的情况下用二分法查找,也要比较数次。数据量较大时比较次数会非常多,IN 的速度就会很慢,而且值集合越大速度越慢。
如果在过滤时不再做比较计算,性能就能得到大幅提高!
首先,确定 IN 可能取值的列表。可能值通常不会太多,一般都保存在一个选项表中。如果没有现成的选项表,要遍历原数据得到所有可能值,保存成一个选项表。然后转换原数据,把 IN 字段值替换为选项表中对应记录的序号(位置),另存成一份新数据。
对替换后的新数据做 IN 判断时,先生成一个与选项表等长的布尔值集合,其第 i 个值由选项表的第 i 个成员是否在 IN 的值集合中决定,在就是 true,不在就是 false。
然后遍历新数据,用 IN 字段值(也就是选项表的序号)去取布尔值集合中的成员,是 true 就符合过滤条件,否则就不符合。
这种方法本质上是将“集合值比较”转换为“序号引用”,省去了比较计算,性能会大幅提升。而且计算时间和值集合大小无关,不会随着 IN 枚举值的增多而增加。
但是,SQL 不支持通过序号(位置)直接取集合中的成员,无法实现这种优化方法。
集算器 SPL 支持序号引用,可以很方便的实现这个优化方法。
1、 数据预处理,转换为序号。
=cs.run(dim1.pos@b(f1):f1),遍历原数据,使用 pos 函数查到原数据 f1 字段在选项表 dim1 中对应记录的序号,用这个序号代替原来的 f1 字段值,另存一份新数据。dim1 预先按照 f1 对应值排好序了,所以这里采用二分法查找,预处理的速度更快。
2、 对预处理好的新数据做 IN 过滤计算。
假设传入值集合为 arg_F1,生成布尔值集合的代码是:
b1=dim1.(arg_F1.contain@b(~)),arg_F1 有序,所以这里也是二分法查找。
然后就可以用新方法过滤新数据了:
=file("T.ctx").open().cursor(…;b1(f1) && …),用 f1 中的序号,直接去取布尔值集合中的成员,成员是 true 则满足过滤条件,否则就不满足。这时不需要再做比较计算,性能会大幅提高。
实测表明,在同等硬件环境下,用 SPL 实现的这种方案比 Spark 上的 SQL 快了上百倍!
更详细的实现方法在这里:SQL 提速:WHERE 中的 IN
还有案例:我们怎样把 X 银行用户画像客群交集计算提速 200+ 倍
英文版