数据库太慢跑崩的一大罪魁

简介: 帐号去重计数是商业分析中的常见需求,通过 SQL 的 COUNT(DISTINCT ...) 实现。然而,当数据量庞大时,COUNT(DISTINCT) 的性能问题凸显,可能导致数据库崩溃。esProc SPL 通过有序数据处理和高效的去重算法,解决了这一难题,尤其适用于复杂的漏斗分析等场景,显著提升了计算效率和资源利用率。

就是非常不起眼的帐号去重计数,用 SQL 写就是 COUNT(DISTINCT …)。

帐号去重计数在商业分析中很常见也有重要的业务意义。这里的帐号可能是用户 ID、银行帐户、手机号、车牌号、…。计算逻辑基本一样,就是从某个时段的历史数据中统计出有多少个帐号满足某个条件。
比如,上个月有多少辆汽车去过北京?上周有多少手机在凌晨 2 点 -4 点之间有过通话?今年收到过海外汇款的银行帐户有多少?…
历史数据通常只会记录发生过的事件,比如某辆车在某时刻在某地出现,某个手机在某时刻和谁通话,某个银行帐户在某日期发生过汇入汇出,…。用指定条件直接筛选历史记录会得到很多帐号相同的记录(某辆车可能多次去过北京,…),但只能按 1 次计数,所以要做去重处理。

去重计数中的筛选条件经常并不会像上面那例子中那么简单。比如,今年发生过周消费总额超过 1000 元的信用卡帐户有多少?需要先统计帐户每周的消费额再来筛选;上月中发生过连续三天消费额超过 100 元的帐户有多少?需要对帐户的交易进行更复杂的存在性判断。不过,无论怎样的条件,最后都归结于计算 COUNT(DISTINCT)。
著名的电商漏斗统计就是这类复杂化的 COUNT(DISTINCT),只不过它会更复杂一些。漏斗统计涉及多步有次序的事件,每一步都对应有一个 COUNT(DISTINCT),以便和上一步的 COUNT(DISTINCT) 一起计算这一步的客户流失率;下一步的 COUNT(DISTINCT) 要在上一步的基础上筛选。并且要考虑事件发生的次序。整个过程比较复杂,但本质上仍然是在做 COUNT(DISTINCT)。

COUNT(DISTINCT) 却一直是数据库计算的难题,通常都会非常慢,如果数据量大(帐号数多,这也是常态),还有可能导致数据库崩掉。
这是为什么呢?

因为 COUNT(DISINCT) 计算量很大。COUNT(DISTINCT id) 需要把遍历过的不同的 id 都保存成一个列表,下一个 id 要和这个列表比对才知道是不是新的,以决定是否要增加计数值并将其添加进列表中。而普通的 COUNT(id) 并不需要保存遍历过的 id,当然也不需要比对,这显然要简单很多。COUNT(DISTINCT) 在 SQL 中的地位看起来和 COUNT,SUM 差不多,但计算速度却要慢得多。
而且,很多数据库在计算 COUNT(DISTINCT id) 时,会把上述的 id 列表放在内存中,这样才能高速的访问和比对,但如果帐号数很多时,内存很可能就装不下,于是跑崩不可避免。如果把这个 id 列表缓存到外存,虽然可以避免崩掉,但访问和比对都不方便,性能会进一步急剧下降。

好在,COUNT(DISTINCT id) 计算中的 id 通常只是一列数据,100G 内存在极限时可能装下几十亿个 id,超出大多数应用的帐号数量了,所以计算常规的 COUNT(DISTINCT) 虽然慢一点,还不致于会频繁跑崩。
但情况再复杂一些就不一定了。比如,漏斗统计会有多个搅和在一起的 COUNT(DISTINCT),写出 SQL 会有嵌套的 JOIN,这时候想跑得快,占用的内存就会大得多(JOIN 也会导致内存和性能之间的严重矛盾),跑崩的概率也就会陡增。

下面是用 SQL 实现的某实际场景的三步漏斗分析:

WITH e1 AS (
    SELECT userid, visittime AS step1_time, MIN(sessionid) AS sessionid, 1 AS step1
    FROM defined_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 defined_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 defined_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 funnel_conversions.step1 IS NOT NULL THEN funnel_conversions.step1_userid ELSE NULL END) AS step1_count,
    COUNT(DISTINCT CASE WHEN funnel_conversions.step2 IS NOT NULL THEN funnel_conversions.step2_userid ELSE NULL END) AS step2_count,
    COUNT(DISTINCT CASE WHEN funnel_conversions.step3 IS NOT NULL THEN funnel_conversions.step3_userid ELSE NULL END) AS step3_count,
    COUNT(DISTINCT CASE WHEN funnel_conversions.step3 IS NOT NULL THEN funnel_conversions.step3_userid ELSE NULL END)
        / COUNT(DISTINCT CASE WHEN funnel_conversions.step1 IS NOT NULL THEN funnel_conversions.step1_userid ELSE NULL END) AS step3_rate
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, tep3
    FROM e1 LEFT JOIN e2 ON e1.userid=e2.userid LEFT JOIN e3 ON e2.userid=e3.userid ) funnel_conversions
LEFT JOIN sessions s ON funnel_conversions.step1_sessionid = s.id
GROUP BY s.devicetype

可以看出,这里不仅有多个 COUNT(DISTINCT),还有多个自关联子查询以实现复杂的漏斗步骤判断。这个 SQL 语句在 Snowflake 的 Medium 级集群(4 节点)三分钟没跑出结果。

那么,该怎么解决这个讨厌的 COUNT(DISTINCT) 呢?
其实并不难,只要把数据按帐号排序后,COUNT(DISINCT) 就很容易算了。
数据对 id 有序时,再计算 COUNT(DISTINCT id) 时,只要保存上一个 id 这一个值就可以了。下一个 id 如果和上一个 id 相等,则增加当前保存 id 的计数,如果不同则替换这个保存的 id 并将计数清 0。不需要在一个大列表中查找比对,只保存一个 id 时占用的内存几乎可以忽略不计。跑得快也不可能崩掉。
对于漏斗这种复杂任务也没问题,数据对 id 有序时,可以每次读入一个 id 的数据进内存,然后可以方便地进行复杂的计算,过程中不会涉及其它 id 的数据,原则上内存只要能装下一个 id 的数据就可以了,同时保存多套 COUNT(DISTINCT) 的计数值也没问题。

可惜,关系数据库和 SQL 做不到这一点。
作为关系数据库理论基础的关系代数是基于无序集合的,SQL 中集合成员(表的记录)没有次序,数据库在存储数据在理论上也不支持有序。上述的优化逻辑在 SQL 中无法实现。

esProc SPL 可以!
esProc SPL 严格地说并不是一个数据库,而是个专业的计算引擎。它不再采用关系代数了,而是自创了以有序集合为基础的离散数据集理论并发明了新程序语言 SPL。esProc 在存储数据时会刻意支持有序,SPL 也提供了丰富的有序计算方法,这样就可以高性能低资源地实现帐号去重统计了。

SPL 的 icount 函数用去重计数,缺省情况会用前述方法实现,即保持一个遍历过的不同 id 的列表,下一个 id 要和列表对比。数据对 id 有序则使用 icount@o,这时候 SPL 就会采用有序去重计数的算法,即只保持上一个 id 值用于比对。
特别地,SPL 还支持有序游标,数据对 id 有序时,可以每次取出 id 相同的一批数据,然后进行复杂的计算以判断当前 id 是否满足筛选条件,之后的计数可以直接用 COUNT,连保持上一个 id 再比对的动作都不需要了(在游标取数时已经比对过了)。

漏斗分析就可以用这个机制实现:
QQ_1732257369940.png

(SPL 代码写在格子里,这和普通程序语言很不像,参考乾学院 写在格子里的程序语言 )

A12 每次读出一个 USERID 的数据进行后续判断,到 A17 再计算时就直接用 count 函数,不需要 icount 了。这个代码不仅更简洁通用(做更多步漏斗只要改 A7,而 SQL 代码要加很多子查询),跑得也更快,使用 Snowflake 同规格的 EC2,只用单台 10 秒即可完成。

其实,去重计数只是个表现,这个问题的实质在于以帐号为单位来切分数据再做处理。按帐号做分组汇总也是类似的运算,而有些汇总并不是简单的 SUM/MAX 这些,有时要很复杂的过程才能计算出来。比如计算每部手机通话时长小于 3 秒的次数;计算上月信用卡帐户的新增积分,规则是有连续三天消费超过 100 元时这几天的积分将加倍;…
信息系统中几乎所有事件性质的数据都会挂在某个帐号下,所以这类运算非常普遍,在各种查询跑批任务中都会碰到,可以说是最常见的业务逻辑模型之一了。有了 esProc SPL 这种基于有序存储上的有序运算,这一大类问题就都可以简洁且高性能的实现了,而对于 SQL 体系的关系数据库即非常困难。

这里举一个变种例子,一个时空碰撞问题,找出和某指定手机在同一时间段和同一地点出现过次数最多的前 20 个手机,数据规模约 250 亿行。SQL 写出来大概是这样:

WITH DT AS ( SELECT DISTINCT id, ROUND(tm/900)+1 as tn, loc FROM T WHERE tm<3*86400)
SELECT * FROM (
    SELECT B.id id, COUNT( DISINCT B.tn ) cnt
    FROM DT AS A JOIN DT AS B ON A.loc=B.loc AND A.tn=B.tn
    WHERE A.id=a AND B.id<>a
    GROUP BY id )
ORDER BY cnt DESC
LIMIT 20

这里又有嵌套的 DISTINCT 运算以及自关联 JOIN,单节点的 ClickHouse 直接崩掉,动用了 5 节点的集群用了 30 多分钟才跑出来。
SPL 代码利用了有序存储和前面提到的有序游标,可以有效避免这些困难,只用一个节点不到 6 分钟就跑完。
QQ_1732257443435.png

细心的读者可能会发现,esProc SPL 的算法有效性依赖于数据对 id 有序,而数据产生次序通常不会是 id,而是时间。那么,这个算法是不是只能应用于事先排序过的历史数据上,对来不及一起排序的新数据就无效了呢?
esProc 已经考虑到这一点,SPL 的复组表可以在数据进入时实现增量排序,实时保证数据在读出时对 id 有序,可以让这套有序计算方案应用到最新的数据上。而且,这类运算通常都会涉及时间区间,SPL 的虚表支持双维有序机制,可以迅速将时间区间外的数据过滤掉,进一步提升运算性能。

esProc 是个纯 Java 软件,能在任何有 JVM 的环境下运算,可以无缝地嵌入到 Java 程序中,非常轻量地将数据仓库的运算能力赋予给各种场景下的应用中。
esProc 提供了可视的开发环境,支持单步执行、设置断点、所见即所得的结果预览,开发调试要比 SQL 和存储过程方便得多。
SPL 还有完善的流程控制语句,像 for 循环,if 分支都不在话下,还支持子程序调用,拥有存储过程才有的过程化能力,可以全面取代 SQL 和存储过程。

最后,esProc SPL 是开源免费的,欢迎前往乾学院了解更多!

相关文章
|
11天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
15天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
6天前
|
并行计算 前端开发 物联网
全网首发!真·从0到1!万字长文带你入门Qwen2.5-Coder——介绍、体验、本地部署及简单微调
2024年11月12日,阿里云通义大模型团队正式开源通义千问代码模型全系列,包括6款Qwen2.5-Coder模型,每个规模包含Base和Instruct两个版本。其中32B尺寸的旗舰代码模型在多项基准评测中取得开源最佳成绩,成为全球最强开源代码模型,多项关键能力超越GPT-4o。Qwen2.5-Coder具备强大、多样和实用等优点,通过持续训练,结合源代码、文本代码混合数据及合成数据,显著提升了代码生成、推理和修复等核心任务的性能。此外,该模型还支持多种编程语言,并在人类偏好对齐方面表现出色。本文为周周的奇妙编程原创,阿里云社区首发,未经同意不得转载。
|
11天前
|
人工智能 运维 双11
2024阿里云双十一云资源购买指南(纯客观,无广)
2024年双十一,阿里云推出多项重磅优惠,特别针对新迁入云的企业和初创公司提供丰厚补贴。其中,36元一年的轻量应用服务器、1.95元/小时的16核60GB A10卡以及1元购域名等产品尤为值得关注。这些产品不仅价格亲民,还提供了丰富的功能和服务,非常适合个人开发者、学生及中小企业快速上手和部署应用。
|
7天前
|
人工智能 自然语言处理 前端开发
用通义灵码,从 0 开始打造一个完整APP,无需编程经验就可以完成
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。本教程完全免费,而且为大家准备了 100 个降噪蓝牙耳机,送给前 100 个完成的粉丝。获奖的方式非常简单,只要你跟着教程完成第一课的内容就能获得。
|
1天前
|
云安全 存储 弹性计算
|
22天前
|
自然语言处理 数据可视化 前端开发
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
合合信息的智能文档处理“百宝箱”涵盖文档解析、向量化模型、测评工具等,解决了复杂文档解析、大模型问答幻觉、文档解析效果评估、知识库搭建、多语言文档翻译等问题。通过可视化解析工具 TextIn ParseX、向量化模型 acge-embedding 和文档解析测评工具 markdown_tester,百宝箱提升了文档处理的效率和精确度,适用于多种文档格式和语言环境,助力企业实现高效的信息管理和业务支持。
3966 5
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
|
11天前
|
算法 安全 网络安全
阿里云SSL证书双11精选,WoSign SSL国产证书优惠
2024阿里云11.11金秋云创季活动火热进行中,活动月期间(2024年11月01日至11月30日)通过折扣、叠加优惠券等多种方式,阿里云WoSign SSL证书实现优惠价格新低,DV SSL证书220元/年起,助力中小企业轻松实现HTTPS加密,保障数据传输安全。
535 3
阿里云SSL证书双11精选,WoSign SSL国产证书优惠
|
10天前
|
数据采集 人工智能 API
Qwen2.5-Coder深夜开源炸场,Prompt编程的时代来了!
通义千问团队开源「强大」、「多样」、「实用」的 Qwen2.5-Coder 全系列,致力于持续推动 Open Code LLMs 的发展。
|
18天前
|
安全 数据建模 网络安全
2024阿里云双11,WoSign SSL证书优惠券使用攻略
2024阿里云“11.11金秋云创季”活动主会场,阿里云用户通过完成个人或企业实名认证,可以领取不同额度的满减优惠券,叠加折扣优惠。用户购买WoSign SSL证书,如何叠加才能更加优惠呢?
999 3