性能优化案例课程 TPCH-Q5

select
	n_name,
	sum(l_extendedprice * (1 - l_discount)) as revenue
from
	customer,
	orders,
	lineitem,
	supplier,
	nation,
	region
where
	c_custkey = o_custkey
	and l_orderkey = o_orderkey
	and l_suppkey = s_suppkey
	and c_nationkey = s_nationkey
	and s_nationkey = n_nationkey
	and n_regionkey = r_regionkey
	and r_name = 'ASIA'
	and o_orderdate >= date '1995-01-01'
	and o_orderdate < date '1995-01-01' + interval '1' year
group by
	n_name
order by
revenue desc;

这是多表关联过滤后的分组聚合运算,其中包括主子表 orders 和 lineitem 关联。

1. 数据存储

大表 orders、lineitem 按照主键 orderkey 有序存储,连接的时候可以有序归并。其他表没有特殊的排序要求,就按照主键有序存储。

这样,就可以继续使用题目 Q3 中的 orders.ctx、lineitem.ctx、customer.ctx。

region、nation、supplier 表直接使用题目 Q2 的 regin.btx,nation.btx 和 supplier.ctx。

将这些表复制到本题的主目录中。

2. 一般实现

计算代码:


A

B

1

=now()


2

1995-1-1

>name="ASIA"

3

=elapse@y(A2,1)


4

=file("region.btx").import@b().select(R_NAME==name)

5

=file("nation.btx").import@b().switch@i(N_REGIONKEY,A4:R_REGIONKEY).keys@i(N_NATIONKEY)

6

=file("supplier.ctx").open().cursor@m(S_SUPPKEY,S_NATIONKEY;A5.find(S_NATIONKEY)).fetch().keys@im(S_SUPPKEY)

7

=file("customer.ctx").open().cursor@m(C_CUSTKEY,C_NATIONKEY;A5.find(C_NATIONKEY)).fetch().keys@im(C_CUSTKEY)

8

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

9

=file("lineitem.ctx").open().news(A8,L_SUPPKEY,L_EXTENDEDPRICE,L_DISCOUNT,O_CUSTKEY;L_SUPPKEY:A6)

10

=A9.select(O_CUSTKEY.C_NATIONKEY==L_SUPPKEY.S_NATIONKEY)

11

=A10.groups@u(L_SUPPKEY.S_NATIONKEY:N_NAME;sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue)

12

=A11.sort@z(revenue).run(N_NAME=A5.find(N_NAME).N_NAME)

这里使用了前面题目中说过的游标建立时过滤、主子表有序关联和将关联字段转换成外键表指针的技巧。

与 Q3 不大相同的是,最后用于分组的字段不是有序的 L_ORDERKEY,所以不能使用 group@s。这里用 groups@u,结果不再按照分组字段有序,性能比不带 @u 会好一些。

A11 中用 S_NATIONKEY 代替 N_NAME 进行分组,因为整数比较比字符串比较效率高,分组结束后再把它替换成 N_NAME。

测试结果:

测试项目

执行时间(秒)

一般实现

18

3. 数据变换

数据变换要采用前面题目提到的维表主键序号化、枚举型字符串字段数字化、日期整数化手段。

region、nation、supplier 表,可以直接使用以前的题目 Q2 转换的 region_2.btx,nation_2.btx 和 supplier_2.ctx。

orders 表,可以使用 Q3 中的 orders_5.ctx。

将这些表复制到本题的主目录中。

customer 转换代码:


A

1

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

2

=file("nation.btx").import@b()

3

=A1.run(C_CUSTKEY=#,C_NATIONKEY=A2.pselect(N_NATIONKEY==C_NATIONKEY))

4

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

5

>A4.append(A3.cursor())

这里使用 Q3 的 customer.ctx 和 Q2 中的 nation.btx。

lineitem 转换代码:


A

1

=file("supplier.ctx").open().import().keys@i(S_SUPPKEY)

2

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

3

=A2.run(L_SUPPKEY=A1.pfind(L_SUPPKEY))

4

=file("lineitem_3.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)

5

>A4.append(A3)

这里使用 Q2 中的 supplier.ctx 和 Q3 中的 lineitem.ctx。

计算代码:


A

B

1

=now()


2

1995-1-1

>name="ASIA"

3

=days@o(elapse@y(A2,1))

=days@o(A2)

4

=file("region_2.btx").import@b(R_NAME).(R_NAME==name)

5

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

6

=A5.(A4(N_REGIONKEY))


7

=file("supplier_2.ctx").open()

=A7.cursor@m().skip().(null)

8

=A7.cursor@m(S_SUPPKEY,S_NATIONKEY;A6(S_NATIONKEY)).fetch().(B7(S_SUPPKEY)=S_NATIONKEY)

9

=file("customer_3.ctx").open()

=A9.cursor@m().skip().(null)

10

=A9.cursor@m(C_CUSTKEY,C_NATIONKEY;A6(C_NATIONKEY)).fetch().(B9(C_CUSTKEY)=C_NATIONKEY)

11

=file("orders_5.ctx").open().cursor@m(O_ORDERKEY,O_CUSTKEY;O_ORDERDATE>=B3 && O_ORDERDATE<A3,O_CUSTKEY:B9:#)

12

=file("lineitem_3.ctx").open().news(A11,L_SUPPKEY,L_EXTENDEDPRICE,L_DISCOUNT,O_CUSTKEY;L_SUPPKEY:B7:#)

13

=A12.select(O_CUSTKEY ==L_SUPPKEY)


14

=A13.groups@u(L_SUPPKEY:N_NAME;sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue)

15

=A14.sort@z(revenue).run(N_NAME=A5(N_NAME).N_NAME)

16

=interval@ms(A1,now())


B7、B9 是对位序列,成员是 true 或者 false。

A11、A12 把 order 的 O_CUSTKEY 赋值为 C_NATIONKEY,把 lineitem 表的 L_SUPPKEY 复制为 S_NATIONKEY。A13 中的 O_CUSTKEY ==L_SUPPKEY,实际上是 C_NATIONKEY 和 S_NATIONKEY 的比较。

测试结果:

测试项目

执行时间(秒)

一般实现

18

数据变换

10

4. 列式计算


A

B

1

=now()


2

1995-1-1

>name="ASIA"

3

=days@o(elapse@y(A2,1))

=days@o(A2)

4

=file("region_2.btx").import@b(R_NAME).(R_NAME==name)

5

=file("nation_2.btx").import@b(N_NATIONKEY,N_NAME,N_REGIONKEY)

6

=A5.(A4(N_REGIONKEY))


7

=file("supplier_2.ctx").open()

=A7.cursor@m().skip().(null)

8

=A7.cursor@mv(S_SUPPKEY,S_NATIONKEY;A6(S_NATIONKEY)).fetch().(B7(S_SUPPKEY)=~)

9

=file("customer_3.ctx").open()

=A9.cursor@m().skip().(null)

10

=A9.cursor@mv(C_CUSTKEY,C_NATIONKEY;A6(C_NATIONKEY)).fetch().(B9(C_CUSTKEY)=~)

11

=file("orders_5.ctx").open().cursor@mv(O_ORDERKEY,O_CUSTKEY;O_ORDERDATE>=B3 && O_ORDERDATE<A3 && B9(O_CUSTKEY))

12

=file("lineitem_3.ctx").open().news(A11,L_SUPPKEY,L_EXTENDEDPRICE,L_DISCOUNT,O_CUSTKEY;B7(L_SUPPKEY))

13

=A12.derive@o(B7(L_SUPPKEY).S_NATIONKEY,B9(O_CUSTKEY).C_NATIONKEY).select@v(C_NATIONKEY==S_NATIONKEY)

14

=A13.groups@u(S_NATIONKEY;sum(L_EXTENDEDPRICE*(1-L_DISCOUNT)):revenue)

15

=A14.sort@z(revenue).new(A5(S_NATIONKEY).N_NAME,revenue)

16

=interval@ms(A1,now())


在列式计算中,要尽量避免使用 switch 操作。A11 中使用 B9(O_CUSTKEY) 仅仅过滤数据,而不再使用 O_CUSTKEY:B9:# 这种关联与过滤同时进行的方法。因为这里 O_CUSTKEY 是序号化的,可以直接在 B9 中通过行号获取 customer 记录而无需关联,可以更好地体现列式计算的优势。

A13 中先用 O_CUSTKEY 在 B9 中的 customer 数据找到对应的记录,然后再取得 S_NATIONKEY 的值。为了达到这个目的,在 B9 中计算出的对位序列就不再是 true 或者 false 了,满足条件的是 customer 记录,不满足条件的是 null。

对于 lineitem 和 supplier 的关联,也是和 customer 类似的处理。

A13 中在对列式游标执行 select 过滤时,加选项 @v 使过滤后的游标仍然是列式游标。

测试结果:

测试项目

执行时间(秒)

一般实现

18

数据变换

10

列式计算

4