从 TPCH 测试学习性能优化技巧之 Q4

一、     查询要求

Q4 语句查询计算给定的某一年的某一季度的按优先权分组的各组订单数量,在每个订单中至少有一行由顾客在它的提交日期之后收到。查询按照优先权的升序列出每一优先权的订单的数量。

Q4语句的特点是:带有分组、排序、聚集操作、子查询并存的单表查询操作。

 

二、     Oracle执行

Oracle编写的查询SQL语句如下:

select  /*+ parallel(n) */

         o_orderpriority,

         count(*) as order_count

from

         orders

where

         o_orderdate >= date '1995-10-01'

         and o_orderdate < date '1995-10-01' + interval '3' month

         and exists (

                   select * from lineitem

                   where l_orderkey = o_orderkey and l_commitdate < l_receiptdate

         )

group by

         o_orderpriority

order by

         o_orderpriority;

其中/*+ parallel(n) */ Oracle的并行查询语法,n是并行数。

脚本执行时间,单位:秒

并行数

1

2

4

8

12

Oracle

504

311

270

189

207

三、     SPL优化

分析SQL语句,它有一个exists子查询,找出子表lineitem中有满足条件l_commitdate < l_receiptdate的记录,对应主表orders的记录。

exists子句都可以改写成join。事实上,SQL在实现时一般也是将exists转换成join来计算,否则,如果按exists的计算逻辑,又会导致N*M的复杂度(NM分别是主子表的记录 数),这种性能完全不能接受。

 

exists改写成join之后是这样:

select  /*+ parallel(n) */

       o_orderpriority,

       count(*) as order_count

from (

       select distinct l_orderkey,o_orderpriority from

       orders,lineitem

       where

              o_orderdate >= date '1995-10-01'

              and o_orderdate < date '1995-10-01' + interval '3' month

              and l_orderkey = o_orderkey

              and l_commitdate < l_receiptdate

)

group by

       o_orderpriority

order by

       o_orderpriority;

 

改写之后可以发现,这仍然是个主子表连接的问题,而且这里的连接运算是为了生成针对主表的某种过滤条件,中间结果集实际上是和orders表对应的,而一般主子连接的结果集是和子表对应的,所以还要对连接结果做针对orderkey的去重运算。

SPL一方面可以采用前面Q3所说的有序归并方法实现连接,另一方面还可以利用结果集的有序性(连接结果集对orderkey有序),快速实现去重运算。

 

SPL脚本如下:


A

1

=now()

2

1995-10-01

3

=elapse@m(A2,3)

4

=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_ORDERPRIORITY;O_ORDERDATE>=A2   && O_ORDERDATE<A3)

5

=file("lineitem.ctx").open().news(A4,L_ORDERKEY,O_ORDERPRIORITY;L_COMMITDATE<L_RECEIPTDATE)

6

=A5.group@1(L_ORDERKEY).groups(O_ORDERPRIORITY;count(1):order_count)

7

return interval@ms(A1,now())

A4,A5使用了前面解释过的技巧。A6中的group@1即表示针对有序游标实现去重(仅分组后取第一条记录)。

 

脚本执行时间,单位:秒

并行数

1

2

4

8

12

Oracle

504

311

270

189

207

SPL组表

160

81

41

24

20

并行性能仍然表现出色。

 

四、     进一步优化

上面分析过程说过,这里的连接运算本质上是为了过滤主表,我们还可以依照这样思路优化:把子表看成主表的集合字段,直接计算聚合值并用作过滤条件。这里可以把统计同一个L_ORDERKEY下满足条件L_COMMITDATE < L_RECEIPTDATE的记录数,如果等于0则将主表相应记录过滤掉,这样将大大减少了连接结果集的记录数,把分组去重的操作换成了效率更高的过滤操作,还能进一步提高性能。

 

SPL脚本如下:


A

1

=now()

2

1995-10-01

3

=elapse@m(A2,3)

4

=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_ORDERPRIORITY;O_ORDERDATE>=A2   && O_ORDERDATE<A3)

5

=file("lineitem.ctx").open().new(A4,O_ORDERPRIORITY,count():c;L_COMMITDATE   < L_RECEIPTDATE).select(c>0)

6

=A5.groups(O_ORDERPRIORITY;count(1):order_count)

7

return interval@ms(A1,now())

A5new函数中计算满足条件的子表记录数量,并过滤保留大于0的。

 

脚本执行时间,单位:秒

并行数

1

2

4

8

12

Oracle

504

311

270

189

207

SPL组表

160

81

41

24

20

优化后

153

78

39

21

15

类似地,这个运算也很容易并行。