第四篇 - 大主子表关联

这一篇介绍主键关联的提速。

主表订单表和子表明细表的关联就是主键关联。SQL 中,这种关联仍用 JOIN 实现,在两个表都很大的情况下,常常出现计算速度非常慢的现象。

如果预先将主子表都按照主键有序存储,就可以使用归并算法实现关联。这种算法只需要对两个表依次遍历,不必借助外存缓存,可以大幅降低计算量和 IO 量。

esProc SPL 支持有序归并算法,可以大幅提升主子表关联计算性能。

先做数据准备,把历史数据从数据库导出为 CTX 文件。在 ETL 中定义 Q4.etl:

..

修改两个表的名字,加上 Q4。

detailsQ4 表按照首字段分段:

..

detailsQ4 表中的 order_id 不唯一,要声明第一个字段是分段键。

防止分段读取时,把某个 order_id 的记录拆分到两段。SPLX 中 create 会自动加 p 选项。

注意:两表要按照 order_id 连接,就按照这个字段有序存储。但不选中“用数据库排序”。

生成 SPL 代码 18:


A

1

=connect("speed")

2

="d:\\speed\\etl\\"

3

=A1.cursor("SELECT order_id,customer_id,employee_id,employee_name,order_date,shipper_id,shipping_fee,shipping_address FROM orders")

4

=A3.sortx(order_id)

5

=file(A2+"ordersQ4.ctx").create@y(#order_id,customer_id,employee_id,employee_name,order_date,shipper_id,shipping_fee,shipping_address).append(A4).close()

6

=A1.cursor("SELECT order_id,product_id,price,quantity FROM details")

7

=A6.sortx(order_id)

8

=file(A2+"detailsQ4.ctx").create@yp(#order_id,product_id,price,quantity).append(A7).close()

9

=A1.close()

A5和 A8 中用 #order_id 来声明 CTX 是对这个字段有序的。

在 details 表中,A8 自动给 create 函数加 @p 选项,表示第一个字段是分段键。防止分段读取时,把某个 order_id 的记录拆分到两段。

例 4.1 指定日期范围,按照客户分组统计订单金额。

select 
    o.customer_id,sum(d.quantity * d.price)
from 
    orders o
join 
    details d on o.order_id = d.order_id
where o.order_date>='2024-01-15'
	and o.order_date<='2024-03-15'
group by o.customer_id;

执行时间 25 秒。

SPL 代码 19:


A

1

=now()

2

=file("ordersQ4.ctx").open().cursor@m(order_id,customer_id;order_date>=date(2024,1,15) && order_date<=date(2024,3,15))

3

=file("detailsQ4.ctx").open().cursor(order_id,quantity,price;;A2)

4

=joinx(A2:o,order_id;A3:d,order_id)

5

=A4.groups(o.customer_id;sum(d.quantity*d.price))

6

>output("query cost:"/interval@ms(A1,now())/"ms")

执行时间 1 秒

A3 中游标的最后一个参数是 A2,表示多线程并行时,details 表会跟随 orders 表分段,保证后面两个表有序归并的正确性。

A4 中 orders 和 details 有序关联归并。

A5 对归并的结果分组汇总。

重点注意 A4:

..

joinx 对 orders 和 details 有序关联归并,结果游标包含两个字段。图中可以看到,每个字段值都是记录对象。

还要注意,两表是一对多关系,订单表数据会被复制。

例 4.2 客户号是 3 或 9 的订单,按照产品号分组统计订单金额。

select 
    d.product_id,sum(d.quantity * d.price)
from 
    orders o
join 
    details d on o.order_id = d.order_id
where o.customer_id =3
	            or o.customer_id=9
group by d.product_id;

执行时间 21 秒。

SPL 代码 20:


A

1

=now()

2

=file("ordersQ4.ctx").open().cursor@m(order_id;customer_id==3 || customer_id==9)

3

=file("detailsQ4.ctx").open().cursor(order_id,quantity,price,product_id;;A2)

4

=joinx(A2:o,order_id;A3:d,order_id)

5

=A4.groups(d.product_id;sum(d.quantity*d.price))

6

>output("query cost:"/interval@ms(A1,now())/"ms")

执行时间 0.6 秒。

例 4.3 求平均每个订单的金额。要求:找产品号不是 2、8 的订单明细,按照日期分组,求平均每个订单的金额。注意:订单数量要去重计数。

select 
    o.order_date,sum(d.quantity * d.price)/count(distinct o.order_id)
from 
    orders o
join 
    details d on o.order_id = d.order_id
where d.product_id !=2
	and d.product_id !=8
group by o.order_date;

执行时间是 40 秒,在主子表关联后计算去重计数,这两种计算 SQL 的性能都不佳。

SPL 代码 21:


A

1

=now()

2

=file("ordersQ4.ctx").open().cursor@m(order_id,order_date)

3

=file("detailsQ4.ctx").open().cursor(order_id,quantity,price;product_id!=2 && product_id!=8;A2)

4

=joinx(A2:o,order_id;A3:d,order_id)

5

=A4.groups(o.order_date;sum(d.quantity*d.price)/icount@o(o.order_id))

6

>output("query cost:"/interval@ms(A1,now())/"ms")

SPL 的有序去重计数性能要好的多,而且有序去重计数正好也需要数据对 order_id 有序,可以在有序归并后顺利实施。

执行时间:1.5 秒

小结一下性能(单位 - 秒):


MYSQL

SPL

例 4.1

25

1

例 4.2

21

0.6

例 4.3

40

1.5

请动手练习一下:

1、找出产品号是 3 或者 6 的订单明细,按照客户分组,求平均每个订单的金额。

2、思考:在自己熟悉的数据库中有没有大主子表关联?是否可以用有序归并方法提速?