第二篇 -COUNT DISTINCT:SPL轻量级文件存储提速查询实践

简介: 在SQL中,COUNT(DISTINCT)因需维护大量分组信息,性能通常较低。而esProc SPL通过将数据按去重字段有序存储,可在遍历过程中高效实现去重计数,无需缓存,大幅提升性能。本文通过多个示例展示了SPL有序去重的具体实现与性能优势。

SQL 中的去重计数 COUNT DISTINCT 一直比较慢。去重本质上是分组运算,需要把遍历过的分组字段值都保持住,用于后续的比对。结果集太大时,还要把数据写到硬盘上做缓存,性能低下。

如果先将数据按照去重字段排序,计算有序去重就会简单很多,只要在遍历过程中,把与上一条不相同的字段保存下来,并把计数值加 1 即可,不需要保持结果集,更不必做外存缓存。

但数据库无法保证存储的次序,很难实施这样的有序去重算法。esProc SPL 可以把数据导出后有序存储,实现这种高性能的有序去重计数。

以事件表 events 为例,数据是这样的:
673370bf9cd75fb07a20dc4634efa20f_1749006571783100.png
event_type 是指操作类型,比如:1 表示 login、2 表示 view、7 表示 confirm 等。

针对事件表的分析计算,一般都是对用户的去重和去重计数。数据外置时,要按照用户号有序存储,可以使用有序去重提高性能。

我们用 ETL 工具定义一个新的 Q2.etl 文件,重复前面的步骤,连接数据库 - 拖拽 events 表 - 编辑:

9351805140a79c471e7f78ef8f1c62ea_1749006571837100.png
这里按照客户号有序存储,可以使用有序去重提高性能。排序字段必须放在第一个。

user_id 不唯一,要选中“按首字段分窜”选项,表示第一个字段是分段键。

防止分段读取时,把某个 user_id 的记录拆分到两段。SPLX 中 create 会自动加 p 选项。

数据库性能较好时选择“用数据库排序”,不选择就用 SPL 排序。

注意:events 和 MYSQL 系统表重名了,所以有些不需要的字段,在 etl 中删掉,不取出即可。

ETL 工具导出 SPL 代码 9:转储成组表 CTX。
image.png
A3 的 SQL 中必须有 order by user_id。

A4 中创建组表时,用 #user_id 声明组表是按照这个字段有序的。这里自动加了 p 选项。

用户有序的数据是什么样的呢?我们来直观的看一下。

运行 SPL 代码 10:
image.png
IDE 右边可以看到:
d9dc995e05a83da1fdfcba953efb7d34_1749006571959100.png
每个用户的数据是连续存放的,读取的时候也是连续的。

每个用户的数据都不多,可以全部读入内存,进行复杂计算。

例 2.1,计算 events 表中有多少不重复的用户。

SQL 语句是这样:

select count(distinct user_id) from events;

执行这个 SQL 需要 7 秒。

SPL 有序去重的方法是这样的:
ddb60aa5af4177334979a22049f4ba02_1749006572037100.png
只要合并相邻且相同的用户,就是去重计数了。

而且,只是计数不做分组汇总的话,可以不建立按照 user_id 分组的子集,性能更好。

SPL 代码 11:
image.png
A2 中游标的 group 函数就是采用有序分组方式,归并相邻且相同的 user_id。

group函数的@1 选项表示每组只取第一条记录,这样就不用建立分组子集了。

这个计算方法,执行时间是 0.1 秒。

例 2.2,出现过 login 或 confirm 的去重 user_id 个数。

event_type 为 1 的,代表 login。event_type 为 7 的,代表 confirm。

SQL 是这样:

select
    count(distinct case when event_type = 1 then user_id end) as count1,
    count(distinct case when event_type = 7 then user_id end) as count2
from events;

执行时间是 5 秒。

SPL 代码 12:

image.png
icount 是去重计数,加 @o 选项就是有序去重计数,也是只合并相邻且相同的值。

if(event_type==1,user_id)):count1,条件成立,得到 user_id,不成立得到 null。

icount 函数会忽略 null,比如:[1,null,1,2,3].icount@o(),结果是 3。

执行时间是 0.15 秒。

例 2.3,去掉重复事件后,发生事件个数大于 5 的用户数量。

同一用户,按照时间顺序连续的事件如果 event_type 相同就是重复事件。比如:用户 31 的一个事件类型是 7,随后发生的事件也是 7 就是重复事件:
4bc606f6d60810c35da50280fcdc0d6c_1749006572159100.png
这一题,SQL 比较难写了,这里不再给出,有兴趣的同学可以尝试写一下。

SPL 代码 13:
image.png
执行时间是 0.5 秒。

A2 中游标的 group 函数,不带任何选项,默认是建立分组子集的,A3 中的 ~ 就表示当前分组子集。

重点看 A3,每组 user_id 数据量不大,可以全组读入内存形成序表 ~,比如这一组:
bd67bedab6d8aae06c96406bc33b6309_1749006572257100.png
~.sort(event_time) 对序表 ~ 按照时间排序后,相邻且相同的 event_type 就是重复数据。再对这个序表继续计算分组 group@o1,归并相邻且相同的 event_type,就去掉了重复。

小结一下性能(单位 - 秒):
image.png
请动手练习一下:

1、计算没有出现过 login 或 confirm 的去重 user_id 个数。

2、思考:是否遇到过去重或者去重计数比较慢的情况?是否可以用有序去重来提速?

相关文章
|
11天前
|
SQL 存储 文件存储
第三篇 - 外键维表的关联:SPL轻量级文件存储提速查询实践
SPL 重构关联计算,区分外键与主键关联,通过序号化、预加载和预关联实现高效运算。相比 SQL 笛卡尔积式 JOIN,SPL 利用关联本质优化存储与计算,显著提升性能,如案例中查询提速数十倍。
|
29天前
|
SQL 并行计算 关系型数据库
第一篇 - 常规过滤及分组汇总:SPL轻量级文件存储提速查询实践
本文以订单表为例,介绍如何使用 esProc SPL 实现数据外置,提升过滤与分组汇总计算效率。通过 SPL 的 ETL 工具导出数据为 BTX 与 CTX 格式,并利用游标、列存、并行计算等技术逐步优化性能,最终执行时间从 MySQL 的 11 秒降至 0.5 秒。适用于处理大数据量、历史数据的高性能分析场景。
|
5月前
|
SQL 存储 OLAP
数据外置提速革命:轻量级开源SPL如何用文件存储实现MPP级性能?
传统交易型数据库在分析计算中常遇性能瓶颈,将数据迁至OLAP数据仓库虽可缓解,但成本高、架构复杂。SPL通过轻量级列存文件存储历史数据,提供强大计算能力,大幅简化架构并提升性能。它优化了列式存储、数据压缩与多线程并行处理,在常规及复杂计算场景中均表现优异,甚至单机性能超越集群。实际案例中,SPL在250亿行数据的时空碰撞问题上,仅用6分钟完成ClickHouse集群30分钟的任务。
数据外置提速革命:轻量级开源SPL如何用文件存储实现MPP级性能?
|
10月前
|
存储 固态存储 文件存储
并行文件存储在大模型训练中的探索与实践
阿里云智能集团存储产品专家何邦剑分享了并行文件存储CPFS在大模型训练中的应用。CPFS针对大模型训练的IO特点,优化性能、降低成本、提升用户体验。它支持多计算平台共享访问,具备数据分层存储、生命周期管理、缓存加速等特性,实现高效的数据处理与管理,显著提升训练效率和资源利用率。尤其在大规模集群中,CPFS提供了高吞吐、低延迟及灵活扩展的能力,助力客户如零一万物实现高性能训练。
|
缓存 监控 NoSQL
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(三)
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(三)
|
存储 缓存 NoSQL
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(四)
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(四)
|
存储 缓存 NoSQL
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(二)
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(二)
|
缓存 NoSQL 算法
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(一)
分布式文件存储与数据缓存 Redis高可用分布式实践(下)(一)
|
缓存 NoSQL Java
分布式文件存储与数据缓存 Redis高可用分布式实践(上)(四)
分布式文件存储与数据缓存 Redis高可用分布式实践(上)(四)
|
存储 缓存 NoSQL
分布式文件存储与数据缓存 Redis高可用分布式实践(上)(三)
分布式文件存储与数据缓存 Redis高可用分布式实践(上)(三)