性能优化案例课程 TPCH-Q14
select
100.00 * sum(
case when p_type like 'PROMO%' then l_extendedprice * (1 - l_discount)
else 0 end)
/ sum(l_extendedprice * (1 - l_discount) ) as promo_revenue
from
lineitem,
part
where
l_partkey = p_partkey
and l_shipdate >= date '1995-04-01'
and l_shipdate < date '1995-04-01' + interval '1' month;
这是两表关联过滤后的聚合运算。
1. 数据存储
lineitem 和 part 表没有什么特殊要求,按照主键有序存储。
继续使用题目 Q3 中的 lineitem.ctx、part.ctx。
将这些表复制到本题的主目录中。
2. 一般实现
这是一个常规的连接后求和计算,充分利用多线程并行即可。
A |
|
1 |
=now() |
2 |
1995-4-1 |
3 |
=elapse@m(A2,1) |
4 |
=file("part.ctx").open().cursor@m(P_PARTKEY,P_TYPE).fetch().keys@i(P_PARTKEY) |
5 |
=file("lineitem.ctx").open().cursor@m(L_PARTKEY,L_EXTENDEDPRICE,L_DISCOUNT;L_SHIPDATE>=A2 &&L_SHIPDATE<A3,L_PARTKEY:A4) |
6 |
=A5.run(L_EXTENDEDPRICE*=(1-L_DISCOUNT),L_DISCOUNT=if(pos@h(L_PARTKEY.P_TYPE,"PROMO"),L_EXTENDEDPRICE,0)) |
7 |
=A6.total(sum(L_DISCOUNT),sum(L_EXTENDEDPRICE)) |
8 |
=100.00*A7(1)/A7(2) |
9 |
=interval@ms(A1,now()) |
代码中用到了前面讲到过的维表属性化、多线程并行等优化方法。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
103 |
part 表数据条数比较多,加上 p_type 是较长的字符串字段,全部读入时占用内存较大。part 读入内存和 lineitem 表连接后,进行字符串比较计算的次数和 lineitem 记录数大致相同。而 lineitem 记录数非常多,所以性能较差。
3. 维表计算提前
上一节,字符串比较计算是在两表连接后进行的。实际上,这是针对维表字段的计算,可以提前完成,写成 SQL 大致是这样的:
select
100.00 * sum(
case when p_type then l_extendedprice * (1 - l_discount)
else 0 end)
/ sum(l_extendedprice * (1 - l_discount) ) as promo_revenue
from
lineitem,
(select
p_partkey,
(case when p_type like 'PROMO%' then true else false end) as p_type
from
part
) p
where
l_partkey = p_partkey
and l_shipdate >= date '1995-04-01'
and l_shipdate < date '1995-04-01' + interval '1' month;
计算代码:
A |
|
1 |
=now() |
2 |
1995-4-1 |
3 |
=elapse@m(A2,1) |
4 |
=file("part.ctx").open().cursor@m(P_PARTKEY,P_TYPE).run(P_TYPE=pos@h(P_TYPE,"PROMO")).fetch().keys@i(P_PARTKEY) |
5 |
=file("lineitem.ctx").open().cursor@m(L_PARTKEY,L_EXTENDEDPRICE,L_DISCOUNT;L_SHIPDATE>=A2 &&L_SHIPDATE<A3,L_PARTKEY:A4) |
6 |
=A5.run(L_EXTENDEDPRICE*=(1-L_DISCOUNT),L_DISCOUNT=if(L_PARTKEY.P_TYPE,L_EXTENDEDPRICE,0)) |
7 |
=A6.total(sum(L_DISCOUNT),sum(L_EXTENDEDPRICE)) |
8 |
=100.00*A7(1)/A7(2) |
9 |
=interval@ms(A1,now()) |
A4 中进行字符串比较计算,计算次数和 part 表记录数相同,比 lineitem 表记录数小很多。最终读入内存的是 1 或者 null,占用内存也比字符串小很多。
A5 不必再做字符串比较计算了。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
103 |
维表计算提前 |
8 |
4. 数据转换
本题中要使用前面介绍的日期整数化优化和维表主键序号化方法。
可以直接使用以前的题目 Q2 中的 part_2.ctx、p_type.btx。
将这些表复制到本题的主目录中。
Q8 转换的 lineitem_4.ctx 实现了 l_partkey 的序号化,但没有完成日期 l_shipdate 的整数化,这里还要转换一下。
转换代码:
A |
|
1 |
=file("lineitem_4.ctx").open().cursor().run(L_SHIPDATE=days@o(L_SHIPDATE)) |
2 |
=file("lineitem_14_4.ctx").create(#L_ORDERKEY,#L_LINENUMBER,L_PARTKEY,L_SUPPKEY,L_QUANTITY,L_EXTENDEDPRICE,L_DISCOUNT,L_TAX,L_RETURNFLAG,L_LINESTATUS,L_SHIPDATE,L_COMMITDATE,L_RECEIPTDATE,L_SHIPINSTRUCT,L_SHIPMODE,L_COMMENT) |
3 |
>A2.append(A1) |
计算代码:
A |
B |
|
1 |
=now() |
|
2 |
1995-4-1 |
PROMO |
3 |
=days@o(elapse@m(A2,1)) |
=days@o(A2) |
4 |
=p_type=file("p_type.btx").import@b().(pos@h(_1,B2)) |
|
5 |
=file("part_2.ctx").open() |
=A5.cursor@m().skip().(false) |
6 |
=A5.cursor@m(P_PARTKEY;A4(P_TYPE)).fetch().(B5(P_PARTKEY)=true) |
|
7 |
=file("lineitem_14_4.ctx").open().cursor@m(L_PARTKEY,L_EXTENDEDPRICE,L_DISCOUNT;L_SHIPDATE>=B3 && L_SHIPDATE<A3) |
|
8 |
=A7.run(L_EXTENDEDPRICE*=(1-L_DISCOUNT),L_DISCOUNT=if(B5(L_PARTKEY),L_EXTENDEDPRICE,0)) |
|
9 |
=A8.total(sum(L_DISCOUNT),sum(L_EXTENDEDPRICE)) |
|
10 |
=100.00*A9(1)/A9(2) |
A4、B5 是对位序列。
Part 表的 p_type 字段已经转换为整数了,A6 不再取出原 p_type 字段的字符串值,而是只取出满足新 p_type 字段条件的 p_partkey 整数值,外存读取量和占用内存都大幅减少,性能提升明显。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
103 |
维表计算提前 |
8 |
数据变换 |
6 |
5. 列式计算
A |
B |
|
1 |
=now() |
|
2 |
1995-4-1 |
PROMO |
3 |
=days@o(elapse@m(A2,1)) |
=days@o(A2) |
4 |
=p_type=file("p_type.btx").import@b().(pos@h(_1,B2)) |
|
5 |
=file("part_2.ctx").open() |
=A5.cursor@m().skip().(false) |
6 |
=A5.cursor@m(P_PARTKEY;A4(P_TYPE)).fetch().(B5(P_PARTKEY)=true) |
|
7 |
=file("lineitem_3.ctx").open().cursor@mv(L_PARTKEY,L_EXTENDEDPRICE,L_DISCOUNT;L_SHIPDATE>=B3 && L_SHIPDATE<A3) |
|
8 |
=A7.derive@o(L_EXTENDEDPRICE*(1-L_DISCOUNT):dp,if(B5(L_PARTKEY),dp,0):dp1) |
|
9 |
=A8.total(sum(dp1),sum(dp)) |
|
10 |
=100.00*A9(1)/A9(2) |
列式计算不能给字段赋值,A8 将上一节的赋值语句改为新增字段 dp 和 dp1。
测试结果:
测试项目 |
执行时间(秒) |
一般实现 |
103 |
维表计算提前 |
8 |
数据转换 |
6 |
列式计算 |
3 |
英文版