比 SQL 快出数量级的大数据计算技术

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: SQL 是大数据计算中最常用的工具,但在实际应用中,SQL 经常跑得很慢,浪费大量硬件资源。例如,某银行的反洗钱计算在 11 节点的 Vertica 集群上跑了 1.5 小时,而用 SPL 重写后,单机只需 26 秒。类似地,电商漏斗运算和时空碰撞任务在使用 SPL 后,性能也大幅提升。这是因为 SQL 无法写出低复杂度的算法,而 SPL 提供了更强大的数据类型和基础运算,能够实现高效计算。

SQL 经常跑得很慢
SQL 是最常用的大数据计算语言,但是,SQL 经常跑得很慢,严重浪费硬件资源。

某银行的反洗钱准备计算,36 亿行,11 节点的 Vertica 集群跑了 1.5 小时。

某电商漏斗运算,3 亿行,Snow Flake 的 Medium 型 4 节点集群超过 3 分钟没算出来。

某时空碰撞任务,250 亿行,Click House 5 节点集群跑了 1800 秒。

这些案例的数据量不算很大,小的只有几 GB,大的也就几百 GB,表现出来的性能却不如人意。

是不是这些任务过于复杂,就只能这么慢呢?并不是,这些任务用 SPL 改写后,性能都有大幅改观。

反洗钱准备计算,SPL 单机只要 26 秒,快了 208 倍,竟然把跑批任务跑成了实时查询!

漏斗运算,SPL 单机 10 秒完成。

时空碰撞任务,SPL 单机只要 350 秒,快了 5 倍。

换用 SPL 后,不仅计算性能快出了数量级,而且硬件还更少了,集群变单机。事实上,连代码也更短了。

为什么 SQL 就跑不出这个性能呢?

由 TOP N 问题探究 SQL 慢的原因
SQL 会跑这么慢,是因为写不出低复杂度的算法。

举个例子,从一亿条数据中取前十名。SQL 写出来有个 order by 字样:

SELECT TOP 10 * FROM Orders ORDER BY Amount DESC
这意味着要排序。如果真做一亿条记录的大排序,那计算量就大了去了。

其实,只要保持一个十条记录的小集合,遍历一亿条数据的时候不断比较、替换小集合中的记录,就可以得到前十名了,计算量比大排序小了 8 倍!遗憾的是,SQL 没有显式的集合数据类型,无法描述这样的计算过程,只能写成 order by,然后指望数据库去优化。还好,几乎所有数据库都会优化,采用更高效的算法,不会傻到真去做大排序。

但是,情况稍复杂些呢?比如我们把需求改成计算分组内的前 10 名,SQL 写出来是这样的。

SELECT * FROM (
    SELECT *, 
       ROW_NUMBER() OVER (PARTITION BY Area ORDER BY Amount DESC) rn  
    FROM Orders )
 WHERE rn<=10

这和前面全量数据取前 10 名的写法相差很大,要借助窗口函数造个组内序号,再用子查询过滤出符合条件的记录,有点“绕”了。

无论如何,SQL 中还是有 ORDER BY,运算逻辑还是排序。实测发现,同样的数据量,Oracle 计算组内前 10 名比全集前 10 名慢出几十倍,按说多个分组应该只慢一点点才对。Oracle 很可能做了排序,甚至是外存排序。

数据库优化引擎面对稍复杂的情况就晕了,只能老老实实按 SQL 的逻辑去执行,常常是个高复杂度的运算逻辑,所以性能就会非常差。

当然,也许哪天数据库又进化了,碰到这种情况也会优化了。但总有更复杂的情况,而数据库优化引擎的进化速度是很慢的。

更复杂的对象事件问题
用 TOP N 来举例,是因为它比较简单易于理解,事实上在现实中并不经常要做这种运算。而且,对于 TOP N 这样的简单问题,即使复杂化到分组 TOP N 的情况,SQL 优化器经过一些改良,也还是能对付的。

但实际情况中的计算常常要远比 TOP N 复杂,前面提到的案例,业务需求都有一定的复杂性,数据量不算大但计算量大。SQL 写不出好算法,只能依靠数据库的优化引擎,而优化引擎却常常靠不住。

比如典型的对象事件问题。这里的对象可以是电商系统用户、银行账号、游戏玩家、手机、车辆等等,通常会有个唯一的 ID,对象涉及的事件都记录在这个 ID 下,例如电商系统操作事件、银行账号的交易记录、手机的通话记录、用户的操作日志等。

对象事件问题的实际计算任务也很多,比如电商系统指定操作步骤的用户流失率,银行账号的交易额超过 1 万元的交易次数、节假日发生的交易额,计算每张信用卡是否连续三天都有超过 1000 元的交易,游戏用户购买某类装备的金额,新注册的游戏用户下次登录时刻的间隔天数,手机通话时间不超过 3 秒的次数,…。

这类问题非常普遍,可以说占了数据分析业务中一半以上的场景。这都是有业务意义的数据分析了,其中有相当一部分会涉及有序运算,用 SQL 都会非常难写,不仅代码繁琐,而且运算效率低下。

下面通过电商漏斗分析这个典型的事件对象问题,继续探究 SQL 慢的原因。

对象事件问题的任务
对象事件问题的任务是针对每个 ID 中符合一定条件的事件,计算出某种聚合值,然后再做 ID 的整体统计。

具体到漏斗分析,就是找出每个用户在一个较短的时间范围,按照次序完成一个给定步骤序列的最多步数。

比如给定三个步骤:浏览商品、查看详情、确认下单,要计算的聚合值是:每个用户在 3 天内按次序最多完成这三个步骤中的几步。有了每一个用户的最多步数,只要简单计数,就可以整体统计出,走到每个步骤的用户数量,进而分析哪一步的用户流失率最严重了。

对象事件计算的特征
要弄清楚 SQL 解决这个问题的难点,先看对象事件计算的几个特征:

一,ID 数量非常多,几千万到几十亿;

二,同一 ID 的事件数并不多,几条到几千条;

三,针对事件的计算复杂,步骤多;

四,计算时 ID 之间无关。

SQL 实现对象事件问题导致大表 JOIN
对象事件之间的复杂关联计算会涉及多条互相依赖的事件记录。

SQL 对跨行运算的支持很弱,要用 JOIN 把多行记录拼到一行中才能进一步运算。而且,参与运算的事件都有条件,一般要先用子查询选出来再 JOIN。计算中涉及的事件越多,参与 JOIN 的子查询越多。

比如下图中的三步漏斗,就涉及 ID1 的多条记录,先用多个子查询筛选合适的事件记录,把多行记录 JOIN 到一行中,才能做进一步运算。

这些子查询还会有依赖性,比如第 2 步事件要在第一步事件的基础上寻找,子查询本身也要用 JOIN 计算。

这些子查询都会基于巨大的事件表,大表 JOIN 本来就很难做,这里还要反复多次 JOIN,跑得慢就不奇怪了,跑崩也很正常。

SQL 实现对象事件问题涉及大结果集 GROUP BY
用 SQL 计算最后聚合值时,又会涉及大结果集的 GROUP BY。

因为期望的结果中,每个 ID 对应一套聚合值,JOIN 的结果并没有这个特征,同一个 ID 可能会 JOIN 出很多条记录。这时候,还要针对 ID 再做一次 GROUP BY。ID 数量非常多,这种大结果集的分组性能非常差,并行计算效果也不好。

有时,计算目标是对 ID 计数,GROUP BY 会退化成 COUNT DISTINCT,但复杂度数量级并没有变。

SQL 实现对象事件问题的脚本
这是一个 SQL 写的三步漏斗分析,其中有多个和 ID 相关的大表 JOIN、GROUP BY 以及 COUNT DISTINCT。

WITH e1 AS (
    SELECT userid, visittime AS step1_time, MIN(sessionid) AS sessionid, 1 AS step1
    FROM events e1 JOIN eventgroup ON eventgroup.id = e1.eventgroup
    WHERE visittime >= DATE_ADD(arg_date,INTERVAL -14 day) AND visittime < arg_date AND eventgroup.name = 'SiteVisit'
    GROUP BY userid,visittime
), e2 AS (
    SELECT e2.userid, MIN(e2.sessionid) AS sessionid, 1 AS step2, MIN(visittime) AS step2_time, MIN(e1.step1_time) AS step1_time
    FROM events e2 JOIN e1 ON e1.sessionid = e2.sessionid AND visittime > step1_time JOIN eventgroup ON eventgroup.id = e2.eventgroup
    WHERE visittime < DATE_ADD(step1_time ,INTERVAL +1 day) AND eventgroup.name = 'ProductDetailPage'
    GROUP BY e2.userid
), e3 AS (
    SELECT e3.userid, MIN(e3.sessionid) AS sessionid, 1 AS step3, MIN(visittime) AS step3_time, MIN(e2.step1_time) AS step1_time
    FROM events e3 JOIN e2 ON e2.sessionid = e3.sessionid AND visittime > step2_time JOIN eventgroup ON eventgroup.id = e3.eventgroup
    WHERE visittime < DATE_ADD(step1_time ,INTERVAL +1 day) AND (eventgroup.name = 'OrderConfirmationType1')
    GROUP BY e3.userid
)
SELECT s.devicetype AS devicetype,
    COUNT(DISTINCT CASE WHEN fc.step1 IS NOT NULL THEN fc.step1_userid  ELSE NULL END) AS step1_count,
    COUNT(DISTINCT CASE WHEN fc.step2 IS NOT NULL THEN fc.step2_userid  ELSE NULL END) AS step2_count,
    COUNT(DISTINCT CASE WHEN fc.step3 IS NOT NULL THEN fc.step3_userid  ELSE NULL END) AS step3_count,
FROM (
    SELECT e1.step1_time AS step1_time, e1.userid AS userid, e1.userid AS step1_userid, e2.userid AS step2_userid,e3.userid AS step3_userid,
           e1.sessionid AS step1_sessionid, step1, step2, step3
    FROM e1 LEFT JOIN e2 ON e1.userid=e2.userid LEFT JOIN e3 ON e2.userid=e3.userid ) fc
LEFT JOIN sessions s ON fc.step1_sessionid = s.id 
GROUP BY s.devicetype

解决对象事件问题的正确思路
其实,只要利用前面说的特征,解决对象事件问题并不难。

我们可以把事件按 ID 排序,每个 ID 的事件不多,每次把一个 ID 的所有事件读入内存。在内存中,使用过程化的语言实现这种复杂计算就很容易了。

因为 ID 之间无关,所以内存关联运算仅在一个 ID 的事件范围内进行,不会有大表 JOIN。每次都会完成一个 ID 聚合值的计算,后续不需要 GROUP BY ID,COUNT DISTINCT 也会变成简单的 COUNT。这个算法不仅计算量少,而且占用内存小,很容易并行。

可惜,用 SQL 无法实现这样的高效算法。这主要是因为 SQL 缺乏离散性,无法实现过程化的复杂跨行运算,只能借助 JOIN。而且,SQL 的集合无序,即使刻意有序存储也无法利用。

SPL 如何计算 Top N
SQL 没有显式的集合数据类型,无法描述前面说的那种低复杂度的算法,只能指望数据库去优化。而 SPL 则可以从算法层面解决这些问题。

SPL 把 Top N 理解为,与 SUM、MAX 一样的聚合运算,只不过返回值是一个集合而已。这样就相当于实现了前面提到的高效算法,代码写出来是这样的:

Orders.groups(;top(10;-Amount))

Orders.groups(Area;top(10;-Amount))

这个语句中没有 order by 字样,也就不会做大排序,而采用小集合的算法了。而且,这里分组前 10 名和全集前 10 名的写法基本一样,只是多了分组键。

SPL 解决对象事件问题
再看对象事件问题,用 SPL 就很容易实现前面说的高效算法。

SPL 的集合有序,能够刻意保证存储的次序,且提供有序游标,可以一次读入一个 ID 的事件数据。SPL 语法中强化了离散性,可以很方便按自然思维写出多步骤跨行运算的代码。

这是 SPL 实现同样漏斗分析的代码:
QQ_1731554732303.png

A8 每次读入一个 ID 的所有事件,然后用 A9 到 A12 四句代码实现复杂判断逻辑,最后 A12 分组统计时也只要简单计数就可以了,不用再考虑去重了。

SPL 解决老大难问题 JOIN
SPL 还能解决 SQL 的老大难问题 JOIN。

SQL 对于 JOIN 的定义并不涉及主键 ,但几乎所有有业务意义的 JOIN 都和主键相关 SQL 无法利用这个特征。采用双边 HASH JOIN 算法不仅计算量大,在外存和集群时的性能还不可控。

SPL 重新定义 JOIN,把 JOIN 分类,可以充分利用主键特征来降低计算量,不仅计算快,也没有不可控的问题。

结果,SPL 的关联表运算性能常常超过 SQL 的宽表。

进一步讨论
SQL 为什么写不出我们期望的高性能算法呢,表面上看是因为对某些运算的支持不足,比如有序计算,分组子集等。更深层原因在于其理论基础 - 关系代数过于简单,缺少必要的数据类型和相应的基础运算。五十年前的理论体系不能表达现代应用的计算需求,也是很正常的。

那么工程上的手段是否可以解决 SQL 慢的问题呢?

比如分布式并行计算。这种手段有时候可以提高性能,但成本太高。

SQL 调优能起到的作用也有限,对于复杂情况不管用。调优确实能通过改变执行路径来采用某些高效算法,但相当多高性能算法在 SQL 中根本没有。

用 UDF 编写高性能代码,原则上可行,但成本太高,可操作性差。集群环境中还会带来复杂的调度和运维问题。而且,如果不能改变数据库存储方案,仅用 UDF 也很难提速。

总之,工程上的优化方法成本高、效果差,没有什么有效手段。SQL 慢是理论层面的问题,工程优化只能有限改善,不能根除。

SPL 则没有继续采用关系代数,而是发明了离散数据集理论,这就会有更丰富的数据类型和基础运算:

图中带星号的都是 SPL 独创的算法。

有了这些基础,就容易写出低复杂度的代码,充分利用硬件资源,实现比 SQL 快出数量级的高性能计算。

SPL 其实提供了集群计算能力,不过在 SPL 的性能优化案例中,大部分场景单机已经足够,集群功能很少有机会得到历练,以至于 SPL 几乎不宣传分布式能力。SPL更多内容欢迎前往乾学院交流沟通!

相关实践学习
基于MaxCompute的热门话题分析
Apsara Clouder大数据专项技能认证配套课程:基于MaxCompute的热门话题分析
相关文章
|
1月前
|
存储 人工智能 大数据
云栖2025|阿里云开源大数据发布新一代“湖流一体”数智平台及全栈技术升级
阿里云在云栖大会发布“湖流一体”数智平台,推出DLF-3.0全模态湖仓、实时计算Flink版升级及EMR系列新品,融合实时化、多模态、智能化技术,打造AI时代高效开放的数据底座,赋能企业数字化转型。
531 0
|
2月前
|
SQL 数据可视化 关系型数据库
MCP与PolarDB集成技术分析:降低SQL门槛与简化数据可视化流程的机制解析
阿里云PolarDB与MCP协议融合,打造“自然语言即分析”的新范式。通过云原生数据库与标准化AI接口协同,实现零代码、分钟级从数据到可视化洞察,打破技术壁垒,提升分析效率99%,推动企业数据能力普惠化。
253 3
|
3月前
|
数据采集 人工智能 分布式计算
ODPS在AI时代的发展战略与技术演进分析报告
ODPS(现MaxCompute)历经十五年发展,从分布式计算平台演进为AI时代的数据基础设施,以超大规模处理、多模态融合与Data+AI协同为核心竞争力,支撑大模型训练与实时分析等前沿场景,助力企业实现数据驱动与智能化转型。
361 4
|
4月前
|
存储 分布式计算 Hadoop
Hadoop框架解析:大数据处理的核心技术
组件是对数据和方法的封装,从用户角度看是实现特定功能的独立黑盒子,能够有效完成任务。组件,也常被称作封装体,是对数据和方法的简洁封装形式。从用户的角度来看,它就像是一个实现了特定功能的黑盒子,具备输入和输出接口,能够独立完成某些任务。
|
2月前
|
SQL 存储 分布式计算
【万字长文,建议收藏】《高性能ODPS SQL章法》——用古人智慧驾驭大数据战场
本文旨在帮助非专业数据研发但是有高频ODPS使用需求的同学们(如数分、算法、产品等)能够快速上手ODPS查询优化,实现高性能查数看数,避免日常工作中因SQL任务卡壳、失败等情况造成的工作产出delay甚至集群资源稳定性问题。
1015 36
【万字长文,建议收藏】《高性能ODPS SQL章法》——用古人智慧驾驭大数据战场
|
1月前
|
数据可视化 大数据 关系型数据库
基于python大数据技术的医疗数据分析与研究
在数字化时代,医疗数据呈爆炸式增长,涵盖患者信息、检查指标、生活方式等。大数据技术助力疾病预测、资源优化与智慧医疗发展,结合Python、MySQL与B/S架构,推动医疗系统高效实现。
|
3月前
|
SQL 分布式计算 大数据
我与ODPS的十年技术共生之路
ODPS十年相伴,从初识的分布式计算到共生进化,突破架构边界,推动数据价值深挖。其湖仓一体、隐私计算与Serverless能力,助力企业降本增效,赋能政务与商业场景,成为数字化转型的“数字神经系统”。
|
3月前
|
存储 人工智能 算法
Java 大视界 -- Java 大数据在智能医疗影像数据压缩与传输优化中的技术应用(227)
本文探讨 Java 大数据在智能医疗影像压缩与传输中的关键技术应用,分析其如何解决医疗影像数据存储、传输与压缩三大难题,并结合实际案例展示技术落地效果。
|
3月前
|
机器学习/深度学习 算法 Java
Java 大视界 -- Java 大数据在智能物流运输车辆智能调度与路径优化中的技术实现(218)
本文深入探讨了Java大数据技术在智能物流运输中车辆调度与路径优化的应用。通过遗传算法实现车辆资源的智能调度,结合实时路况数据和强化学习算法进行动态路径优化,有效提升了物流效率与客户满意度。以京东物流和顺丰速运的实际案例为支撑,展示了Java大数据在解决行业痛点问题中的强大能力,为物流行业的智能化转型提供了切实可行的技术方案。
下一篇
oss云网关配置