用 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