上千个标签上的统计查询如何跑得快?
标签是指用“是否”型数据来实现某种标记,计算时要过滤出指定几个标签都为“是”的数据,再做进一步统计。比如:用标签表示客户是否为白领、是否活跃、是否注册等等,要统计“活跃”、“注册”两个标签都为“是”的客户数量。
标签在数据库中一般用布尔型字段来存储。如果只有几个或几十个,那就简单地把过滤条件写在 WHERE 中就可以了。但实际应用中,标签的总数有可能达到成百上千个。数据库表不支持这么多字段,要分成多个表,再做 JOIN,在数据量很大时,大表连接的性能非常差。
为了避免大表连接,还可以把几千个布尔字段转列为行,用一个“标签号”字段存储,计算时先分组再过滤、统计。但这个分组结果集很大,需要外存缓存,性能还是很差。
如果用整数的二进制位来存储标签(0 代表否,1 代表是),那么 16 位小整数就能存 16 个标签,100 个小整型字段就能存 1600 个标签,可以有效减少数据量,避免大表连接。
但是,数据库不支持一位相当于一列的按位存储、计算,无法实现这种性能优化方法。
集算器 SPL 支持更丰富的存储格式和运算逻辑,可以方便地实现这个方案。
1、预处理数据,转换为按位存储。
=cs.new(…,bits(flag1,flag2,…,flag16):f1),使用 bits 函数即可把原来的 16 个标签字段 flag1,…,flag16(取值为 0 和 1)转换成一个 16 位整数,即将 16 个字段转换成一个字段。
比如,flag4=1,flag7=1,其它都是 0,则转换出来 f1 即是二进制数 0001001000000000,用十进制写出来是 4608,也就是用一个整数 4608 表示了这 16 个标签的取值。
2、对预处理好的数据进行标签过滤、统计。
假设要过滤出标签 flag4、flag8 为 1 的数据,就要判断转换后的 f1 字段中第 4、8 位是否为 1。用位运算可以很容易判断,即用二进制数 0001000100000000 和 f1 做对位“相与”运算,结果仍然是这个二进制数则表示相应的位都是 1。即 and(f1,4352)==4352,其中 4352 就是二进制数 0001000100000000 转换成十进制的数值。
针对标签的过滤运算可以写成这样的形式:
=file("T.ctx").open().cursor(id,…;and(f1,4352)==4352 && …)
这时不需要做大表 JOIN,只要进行简单的位运算就可以一次判断出多个标签的条件,性能可以大幅提升。
实测表明,在同等硬件环境下,用 SPL 实现的这种方案比 Spark 上的 SQL 快了 200 倍!
更详细的实现方法在这里:SQL 提速:二值标签
还有案例:我们怎样把 X 银行用户画像客群交集计算提速 200+ 倍
英文版