用 TPCH 练习性能优化 Q3

一、 SQL及分析

查询SQL语句如下:

select * from (
    select
        l_orderkey,
        sum(l_extendedprice * (1 - l_discount)) as revenue,
        o_orderdate,
        o_shippriority
    from
        customer,
        orders,
        lineitem
    where
        c_mktsegment = 'BUILDING'
        and c_custkey = o_custkey
        and l_orderkey = o_orderkey
        and o_orderdate < date '1995-03-15'
        and l_shipdate > date '1995-03-15'
    group by
        l_orderkey,
        o_orderdate,
        o_shippriority
    order by
        revenue desc,
        o_orderdate
) where rownum<=10;

这是典型的主子表关联,关联后进行分组统计。

二、 SPL实现

主子表的主键经过排序后,可以采用有序归并的算法实现关联,比较次数要少很多,而且很容易并行。

另外,本例GROUP BY子句中有三个字段:L_ORDERKEYO_ORDERDATEO_SHIPPRIORITY,原因是结果集中要输出这三个字段。事实上,后两个字段可以被第一个字段决定,只要针对第一个字段进行分组,另两个字段可根据第一个字段计算出来,不需要一起参与对比。


A

1

=now()

2

1995-3-15

3

>mktsegment="BUILDING"

4

=file("customer.ctx").open().cursor@m(C_CUSTKEY;C_MKTSEGMENT==mktsegment).fetch().keys@im(C_CUSTKEY)

5

=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_ORDERDATE,O_SHIPPRIORITY;O_ORDERDATE<A2 && A4.find(O_CUSTKEY))

6

=file("lineitem.ctx").open().news@r(A5,O_ORDERKEY, sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue,O_ORDERDATE,O_SHIPPRIORITY;L_SHIPDATE>A2)

7

=A6.total(top(10;-revenue,O_ORDERDATE))

8

=interval@ms(A1,now())

A6也使用了游标建立时过滤的技巧。在A5中也有这种技巧,其中的A4.find表示只需要把外键不匹配的记录过滤掉,而不再转换成指针。

A6中对主子表做有序归并时,因为主表order已被过滤,而主子表的主键同序,这时可以用news方法读取子表时跳过主表中已经过滤掉的键值,从而减少子表的读取量。选项@r表示对子表记录按主表主键分组做聚合运算。

需要强调的是,lineitem必须按orders匹配的方法分段存储,即开始准备数据时lineitem创建组表用了@p,这样才能保证并行分段时不会错位关联。

三、 进一步优化

1. 优化方法

本例中要使用Q2中介绍的维表预加载(加载customer)、维表主键序号化(customer中的C_CUSTKEYorders中的O_CUSTKEY),还要使用Q1中介绍的字符串整数化(customer中的C_MKTSEGMENT)、日期整数化(orders中的O_ORDERDATE)等优化方法。lineitem表中的L_SHIPDATE已在Q1中转换过了。

2. 数据转换代码

2.1 customer转换


A

1

=file("customer.ctx").open().import()

2

=A1.id(C_MKTSEGMENT).sort()

3

=file("c_mktsegment.txt").export(A2)

4

=A1.run(C_CUSTKEY=#, C_MKTSEGMENT=A2.pos@b(C_MKTSEGMENT))

5

=file("customer_3.ctx").create(#C_CUSTKEY, C_NAME, C_ADDRESS, C_NATIONKEY, C_PHONE, C_ACCTBAL, C_MKTSEGMENT, C_COMMENT)

6

>A5.append(A4.cursor())

A2/A3 C_MKTSEGMENT做字符串整数化

A4 设主键C_CUSTKEY为行号,设C_MKTSEGMENT为它在取值列表中的序号

2.2 orders转换


A

1

=file("customer.ctx").open().import(C_CUSTKEY).keys@im(C_CUSTKEY)

2

=file("orders.ctx").open().cursor()

3

=A2.run(O_CUSTKEY=A1.pfind(O_CUSTKEY),O_ORDERDATE=days@o(O_ORDERDATE))

4

=file("orders_3.ctx").create(#O_ORDERKEY,O_CUSTKEY,O_ORDERSTATUS,O_TOTALPRICE, O_ORDERDATE,O_ORDERPRIORITY,O_CLERK,O_SHIPPRIORITY,O_COMMENT)

5

>A4.append(A3)

A1 读取customer表中C_CUSTKEY为主键并创建索引

A3 A1中查找主键值为O_CUSTKEY的记录所在行号,将它给O_CUSTKEY。将O_ORDERDATE转换为小整数

2.3 lineitem转换

使用Q1中转换过的lineitem_1.ctx,重命名为lineitem_3.ctx

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

维表和字符串字段取值列表需要预加载,加载代码如下:


A

1

>env(customer, file("customer_3.ctx").open().import())

2

>env(c_mktsegment,file("c_mktsegment.txt").import@si())

在查询计算之前,需要先运行预加载代码,将小维表读入内存。

计算代码:


A

1

=now()

2

=days@o(date("1995-03-15"))

3

>mktsegment=c_mktsegment.pos@b("BUILDING")

4

=customer.@m(C_MKTSEGMENT==mktsegment)

5

=file("orders_3.ctx").open().cursor@m(O_ORDERKEY,O_ORDERDATE,O_SHIPPRIORITY;O_ORDERDATE<A2 && A4(O_CUSTKEY))

6

=file("lineitem_3.ctx").open().news@r(A5,O_ORDERKEYsum(,L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue,O_ORDERDATE,O_SHIPPRIORITY;L_SHIPDATE>A2)

7

=A6.total(top(10;-revenue,O_ORDERDATE))

8

=A7.run(O_ORDERDATE=date@o(O_ORDERDATE))

9

return interval@ms(A1,now())

A4 主键序号化后使用对位序列,优化原理参看Q2

A8 将整数化后的日期用date@o再还原成原始日期

四、 使用企业版列式计算

1. 原始数据


A

1

=now()

2

1995-3-15

3

>mktsegment="BUILDING"

4

=file("customer.ctx").open().cursor@mv(C_CUSTKEY;C_MKTSEGMENT==mktsegment).fetch().keys@im(C_CUSTKEY)

5

=file("orders.ctx").open().cursor@mv(O_ORDERKEY,O_ORDERDATE,O_SHIPPRIORITY;A4.find(O_CUSTKEY) && O_ORDERDATE<A2)

6

=file("lineitem.ctx").open().news@r(A5,O_ORDERKEY,sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue,O_ORDERDATE,O_SHIPPRIORITY;L_SHIPDATE>A2)

7

=A6.total(top(10;-revenue,O_ORDERDATE))

8

=interval@ms(A1,now())

A4A5cursor函数都加选项@v,表示使用列式游标读数。A6中使用news函数与A5创建同步游标,它也与同步的A5一样是列式游标。

2. 优化后数据

维表和字符串字段取值列表需要预加载,加载代码如下:


A

1

>env(customer, file("customer_3.ctx").open().import@v())

2

>env(c_mktsegment,file("c_mktsegment.txt").import@si())

加载维表数据时都加上选项@v,将维表读成列式序表。

在查询计算之前,需要先运行预加载代码,将小维表读入内存。

计算代码:


A

1

=now()

2

=days@o(date("1995-03-15"))

3

>mktsegment=c_mktsegment.pos@b("BUILDING")

4

=customer.(C_MKTSEGMENT==mktsegment)

5

=file("orders_3.ctx").open().cursor@mv(O_ORDERKEY,O_ORDERDATE,O_SHIPPRIORITY;A4(O_CUSTKEY) && O_ORDERDATE<A2)

6

=file("lineitem_3.ctx").open().news@r(A5,O_ORDERKEY,sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue,O_ORDERDATE,O_SHIPPRIORITY;L_SHIPDATE>A2)

7

=A6.total(top(10;-revenue,O_ORDERDATE))

8

=A7.new(O_ORDERKEY,revenue,date@o(O_ORDERDATE):O_ORDERDATE,O_SHIPPRIORITY)

9

=interval@ms(A1,now())

A8中要将整数化的日期再转回日期,常规的做法是用run函数重设O_ORDERDATE的值,但列式中不能改变列的数据类型,所以这里要用new

五、 测试结果

单位:秒


常规

列式

优化前

12.6

8.4

优化后

8.9

5.8