SELECT mod(id, 100) AS Aid, max(amount) AS Amax
FROM test.t
GROUP BY mod(id, 100)
SELECT * FROM test.t ORDER BY amount DESC LIMIT 100
对比测试结果是这样的: 单看 CH 的 SQL2,常规 TopN 的计算方法是全排序后取出前 N 条数据。数据量很大时,如果真地做全排序,性能会非常差。SQL2 的测试结果说明,CH 应该和 SPL 一样做了优化,没有全排序,所以两者性能都很快,SPL 稍快一些。 也就是说,无论简单运算还是复杂运算,esProc SPL 都能更胜一筹。 差距还不止于此。 正如前面所说,CH 和 ORA 使用 SQL 语言,都是基于关系模型的,所以都面临 SQL 优化的问题。TPC-H 测试证明,ORA 能优化的一些场景 CH 却优化不了,甚至跑不出结果。那么,如果面对一些 ORA 也不会优化的计算,CH 就更不会优化了。比如说我们将 SQL1 的简单分组汇总,改为两种分组汇总结果再连接,CH 的 SQL 写出来大致是这样: SQL3:
SELECT *
FROM (
SELECT mod(id, 100) AS Aid, max(amount) AS Amax
FROM test.t
GROUP BY mod(id, 100)
) A
JOIN (
SELECT floor(id / 200000) AS Bid, min(amount) AS Bmin
FROM test.t
GROUP BY floor(id / 200000)
) B
ON A.Aid = B.Bid
这种情况下,对比测试的结果是 CH 的计算时间翻倍,SPL 则不变:
这是因为 SPL 不仅使用了列式游标,还使用了遍历复用机制,能在一次遍历过程中计算出多种分组结果,可以减少很多硬盘访问量。CH 使用的 SQL 无法写出这样的运算,只能靠 CH 自身的优化能力了。而 CH 算法优化能力又很差,其优化引擎在这个测试中没有起作用,只能遍历两次,所以性能下降了一倍。 SPL 实现遍历复用的代码很简单,大致是这样:
A | B | |
1 | =file("topn.ctx").open().cursor@mv(id,amount) | |
2 | cursor A1 | =A2.groups(id%100:Aid;max(amount):Amax) |
3 | cursor | =A3.groups(id200000:Bid;min(amount):Bmin) |
4 | =A2.join@i(Aid,A3:Bid,Bid,Bmin) |
再将 SQL2 常规 TopN 计算,调整为分组后求组内 TopN。对应 SQL 是:
SELECT
gid,
groupArray(100)(amount) AS amount
FROM
(
SELECT
mod(id, 10) AS gid,
amount
FROM test.topn
ORDER BY
gid ASC,
amount DESC
) AS a
GROUP BY gid
这个分组 TopN 测试的对比结果是下面这样的:
CH 做分组 TopN 计算比常规 TopN 慢了 42 倍,说明 CH 在这种情况下很可能做了排序动作。也就是说,情况复杂化之后,CH 的优化引擎又不起作用了。与 SQL 不同,SPL 把 TopN 看成是一种聚合运算,和 sum、count 这类运算的计算逻辑是一样的,都只需要对原数据遍历一次。这样,分组求组内 TopN 就和分组求和、计数一样了,可以避免排序计算。因此,SPL 计算分组 TopN 比 CH 快了 22 倍。 而且,SPL 计算分组 TopN 的代码也不复杂:
A | |
1 | =file("topn.ctx").open().cursor@mv(id,amount) |
2 | =A1.groups(id%10:gid;top(10;-amount)).news(#2;gid,~.amount) |
A | B | |
1 | =["etype1","etype2","etype3"] | =file("event.ctx").open() |
2 | =B1.cursor(id,etime,etype;etime>=date("2021-01-10") && etime<date("2021-01-25") && A1.contain(etype) && …) | |
3 | =A2.group(id).(~.sort(etime)) | =A3.new(~.select@1(etype==A1(1)):first,~:all).select(first) |
4 | =B3.(A1.(t=if(#==1,t1=first.etime,if(t,all.select@1(etype==A1.~ && etime>t && etime<t1+7).etime, null)))) | |
5 | =A4.groups(;count(~(1)):STEP1,count(~(2)):STEP2,count(~(3)):STEP3) |
CH 的 SQL 无法实现这样的计算,我们以 ORA 为例看看三步漏斗的 SQL 写法:
with e1 as (
select gid,1 as step1,min(etime) as t1
from T
where etime>= to_date('2021-01-10', 'yyyy-MM-dd') and etime<to_date('2021-01-25', 'yyyy-MM-dd')
and eventtype='eventtype1' and …
group by 1
),
with e2 as (
select gid,1 as step2,min(e1.t1) as t1,min(e2.etime) as t2
from T as e2
inner join e1 on e2.gid = e1.gid
where e2.etime>= to_date('2021-01-10', 'yyyy-MM-dd') and e2.etime<to_date('2021-01-25', 'yyyy-MM-dd')
and e2.etime > t1
and e2.etime < t1 + 7
and eventtype='eventtype2' and …
group by 1
),
with e3 as (
select gid,1 as step3,min(e2.t1) as t1,min(e3.etime) as t3
from T as e3
inner join e2 on e3.gid = e2.gid
where e3.etime>= to_date('2021-01-10', 'yyyy-MM-dd') and e3.etime<to_date('2021-01-25', 'yyyy-MM-dd')
and e3.etime > t2
and e3.etime < t1 + 7
and eventtype='eventtype3' and …
group by 1
)
select
sum(step1) as step1,
sum(step2) as step2,
sum(step3) as step3
from
e1
left join e2 on e1.gid = e2.gid
left join e3 on e2.gid = e3.gid
总结一下:CH 计算某些简单场景(比如单表遍历)确实很快,和 SPL 的性能差不多。但是,高性能计算不能只看简单情况快不快,还要权衡各种场景。对于复杂运算而言,SPL 不仅性能远超 CH,代码编写也简单很多。SPL 能覆盖高性能数据计算的全场景,可以说是完胜 CH。
原文来自「逛逛GitHub」|侵删
原文始发于微信公众号(寰宇卫士):一个比 ClickHouse 还快的开源数据库
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论