性能优化案例课程 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