用 TPCH 练习性能优化 Q1

一、 SQL及分析

查询SQL语句如下:

select
    l_returnflag,
    l_linestatus,
    sum(l_quantity) as sum_qty,
    sum(l_extendedprice) as sum_base_price,
    sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,
    sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,
    avg(l_quantity) as avg_qty,
    avg(l_extendedprice) as avg_price,
    avg(l_discount) as avg_disc,
    count(*) as count_order
from
    lineitem
where
    l_shipdate <= date '1995-12-01' - interval '90' day(3)
group by
    l_returnflag,
    l_linestatus
order by
    l_returnflag,
    l_linestatus;

这是个小结果集的常规分组汇总。

二、 SPL实现

小结果集的常规分组汇总直接用groups


A

1

=now()

2

1995-12-01

3

=A2-90

4

=file("lineitem.ctx").open().cursor@m(L_SHIPDATE,L_QUANTITY, L_EXTENDEDPRICE,L_DISCOUNT,L_TAX,L_RETURNFLAG,L_LINESTATUS;L_SHIPDATE<=A3)

5

=A4.groups(L_RETURNFLAG, L_LINESTATUS; sum(L_QUANTITY):sum_qty, sum(L_EXTENDEDPRICE):sum_base_price, sum(dp=L_EXTENDEDPRICE*(1-L_DISCOUNT)):sum_disc_price, sum(dp*L_TAX):sum_charge, avg(L_QUANTITY):avg_qty, avg(L_EXTENDEDPRICE):avg_price, avg(L_DISCOUNT):avg_disc, count(1):count_order)

6

=A5.run(sum_charge+=sum_disc_price)

7

=interval@ms(A1,now())

A4中用@m表示生成多路游标并行计算,将过滤条件写在游标中能避免不满足条件的记录被生成。

A5中计算时,打折价格被两次使用,先计算出来赋给变量dp避免重复计算。

A6中分组完成后再做run把价税加起来,减少加法次数(分组后的记录数要远少于原表记录数)。

三、 进一步优化

1. 优化方法

1.1 日期整数化

日期型数据通常是long类型,其计算和比较都较为费时,将日期用days@o函数转换成小整数,可以提高性能。本例中的L_SHIPDATE字段是日期型数据,将它转换成一个小整数进行优化。

1.2 字符串整数化

字符串的运算相对于整数来说也更费时,如果数据表行数很多,某字符串字段的不同取值较少,可以把这些不同取值构成一个列表,把字段值替换成串值在列表中对应的序号,从而变成整数型字段以提高运算的效率。本例中的分组字段L_RETURNFLAGL_LINESTATUS是字符串字段,它们的取值个数分别为3个和2个,可以转换成整数字段。

2. 数据转换代码


A

1

=file("lineitem.ctx").open().cursor(L_RETURNFLAG, L_LINESTATUS)

2

=A1.id(L_RETURNFLAG, L_LINESTATUS).(~.sort())

3

=file("l_returnflag.txt").export(A2(1))

4

=file("l_linestatus.txt").export(A2(2))

5

=file("lineitem.ctx").open().cursor().run(L_RETURNFLAG =A2(1).pos@b(L_RETURNFLAG), L_LINESTATUS =A2(2).pos@b(L_LINESTATUS), L_SHIPDATE=days@o(L_SHIPDATE))

6

=file("lineitem_1.ctx").create@p(#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)

7

>A6.append(A5)

A2 计算两个字符串字段的取值列表并排序

A5 pos@b查找相应字符串在列表中的序号,同时将L_SHIPDATE字段的值用days@o变换为小整数。

3. 数据转换后的计算代码


A

1

>l_linestatus=file("l_linestatus.txt").import@si()

2

>l_returnflag=file("l_returnflag.txt").import@si()

3

=now()

4

=l_linestatus.len()+1

5

1995-12-01

6

=days@o(A5-90)

7

=file("lineitem_1.ctx").open().cursor@m(L_QUANTITY, L_EXTENDEDPRICE,L_DISCOUNT,L_TAX,L_RETURNFLAG,L_LINESTATUS;L_SHIPDATE<=A6)

8

=A7.groups(L_RETURNFLAG*A4+L_LINESTATUS:gk; sum(L_QUANTITY):sum_qty, sum(L_EXTENDEDPRICE):sum_base_price, sum(dp=L_EXTENDEDPRICE*(1-L_DISCOUNT)):sum_disc_price, sum(dp*L_TAX):sum_charge,sum(L_DISCOUNT):sum_disc, count(1):count_order)

9

=A8.new(l_returnflag(gk\A4):L_RETURNFLAG,l_linestatus(gk%A4):L_LINESTATUS,sum_qty,sum_base_price,sum_disc_price,sum_charge+sum_disc_price:sum_charge,sum_qty/count_order:avg_qty,sum_base_price/count_order:avg_price,sum_disc/count_order:avg_disc,count_order)

10

=interval@ms(A3,now())

A1/A2 读出L_LINESTATUSL_ RETURNFLAG字段对应的串值列表

A6 将过滤条件的日期也变换成小整数

A8 L_ RETURNFLAGL_LINESTATUS的值计算出一个新的分组键值gk,在groups函数中就把按2个字段分组变成按一个字段gk分组,可以加快分组的速度。分组统计值中不计算avg_qtyavg_priceavg_disc,而是在A9中通过相应的sum值和count值计算出来,可以减少分组统计中的计算量。

A9 gk返算出L_ RETURNFLAGL_LINESTATUS的值,并换成它们在列表中对应的串值

四、 使用企业版列式计算

1. 原始数据


A

1

=now()

2

1995-12-01

3

=A2-90

4

=file("lineitem.ctx").open().cursor@mv(L_QUANTITY, L_EXTENDEDPRICE,L_DISCOUNT,L_TAX,L_RETURNFLAG,L_LINESTATUS;L_SHIPDATE<=A3)

5

=A4.derive@o(L_EXTENDEDPRICE*(1-L_DISCOUNT):dp)

6

=A5.groups(L_RETURNFLAG, L_LINESTATUS; sum(L_QUANTITY):sum_qty, sum(L_EXTENDEDPRICE):sum_base_price,sum(dp):sum_disc_price, sum(dp*L_TAX):sum_charge, avg(L_QUANTITY):avg_qty, avg(L_EXTENDEDPRICE):avg_price, avg(L_DISCOUNT):avg_disc, count(1):count_order)

7

=A6.new(L_RETURNFLAG,L_LINESTATUS,sum_qty,sum_base_price,sum_disc_price,sum_disc_price+sum_charge:sum_charge,avg_qty,avg_price,avg_disc,count_order)

8

=interval@ms(A1,now())

A4中选项@v表示使用列式游标

列式游标中要避免在计算过程中产生新的变量,推荐使用derive方法将新变量变成新的列,如A5中所示,选项@o表示新列增加到原序表上,不产生新的。

A7中没有使用run函数,run适合用于行式序表,列式中尽量避免使用。

2. 优化后数据


A

1

>l_linestatus=file("l_linestatus.txt").import@si()

2

>l_returnflag=file("l_returnflag.txt").import@si()

3

=now()

4

=l_linestatus.len()+1

5

1995-12-01

6

=days@o(A5-90)

7

=file("lineitem_1.ctx").open().cursor@mv(L_QUANTITY, L_EXTENDEDPRICE,L_DISCOUNT,L_TAX,L_RETURNFLAG,L_LINESTATUS;L_SHIPDATE<=A6)

8

=A7.derive@o(L_EXTENDEDPRICE*(1-L_DISCOUNT):dp,L_RETURNFLAG*A4+L_LINESTATUS:gk)

9

=A8.groups(gk;sum(L_QUANTITY):sum_qty, sum(L_EXTENDEDPRICE):sum_base_price,sum(dp):sum_disc_price,sum(dp*L_TAX):sum_charge, sum(L_DISCOUNT):sum_disc, count(1):count_order)

10

=A9.new(l_returnflag(gk\A4):L_RETURNFLAG,l_linestatus(gk%A4):L_LINESTATUS,sum_qty,sum_base_price,sum_disc_price,sum_disc_price+sum_charge:sum_charge,sum_qty/count_order:avg_qty,sum_base_price/count_order:avg_price,sum_disc/count_order:avg_disc,count_order)

11

=interval@ms(A3,now())

A8中同时创建dpgk两个新列

五、 测试结果

单位:秒


常规

列式

优化前

22.2

11.1

优化后

18.6

7.3