SPL 附表
在大数据表关联场景中,如果各个表按照主键或部分主键关联,我们可以采用 SPL 附表机制来做性能优化。
附表机制是将上述要关联的表绑在一起存储。以客户表 customer 和联系方式表 contact 为例,两表关系如下图 1:
图 1 客户和联系方式表关系
图 1 中,客户表和联系表是通过各自的主键关联的,我们称这样关联的两个表互为同维表。
采用 SPL 附表机制存储客户表和联系表数据的方案,大致是下图 2 这样:
图 2 客户 - 联系表的附表存储方案
客户表用来存储客户的主要数据,所以主键 cid 字段值应该是最完整的。而有些客户可能并没有记录联系方式,因此联系方式表中的 cid 值则有可能不全。要绑定在一起存储时,我们以客户表为基准,称为基表,联系方式表称为附表。
图 1 中,先建立基表,附表的字段则作为基表记录的附加字段存储。附表字段也可以认为是基表的字段,只是这些字段在很多记录中是空值。新建的 customer_contact 表,是基表和附表的组合,称为组表。组表中的基表和附表称为实表。
对于三个或更多的同维表,可以存储成一个基表和多个附表。存储方案和图 2 类似,只是有更多的附加字段而已。
附表存储机制在性能方面有以下优势:
1) 客户表(基表)和联系表(附表)有共同的主键 cid。附表字段是基表记录的附加字段,基表中存储 cid 就可以了,附表不需要再存储 cid,存储量会更小。采用列存方式时,关联计算需要读取的数据量也变少了。
2) 附表字段可以作为基表记录的附加字段直接被引用,比如联系表中的 phone_num 和 address 字段。不需要再做两个表的关联比对,计算量会变小。特别地,如果基表被过滤了,附表会自然被过滤,不需要再做附表过滤计算了,反过来也成立。
3) 附表字段作为基表记录的字段,和基表记录绑在一起,分段时天然同步,不需要再做特殊的跟随分段。有利于并行计算提速。
同维表是通过各表的主键关联的。如果是一个表的主键和其他表的部分主键关联,情况会复杂一些。以订单表 orders、明细表 details、付款表 payments 为例,三个表的关系如下图 3:
图 3 订单表、明细表、付款表的关系
图 3 中,三个表要按照订单编号 oid 关联。oid 是订单表的主键,是另外两个表的部分主键。我们将这样关联的表称为主子表,订单是主表,明细和付款是子表。
将三个表数据转换为 SPL 附表机制存储,大概是下图 4 这样:
图 4 订单 - 明细 - 付款表的附表存储机制
这时,要以主表为基表,子表为附表。图 4 中,先建立基表,再用附表的字段作为基表记录的附加字段。由于订单的一条记录对应明细表或者付款表的多条记录,所以这些附加字段的取值是个集合。来自同一个附表的附加字段,其取值集合的长度是相同的,比如附表明细表的 price 字段和 quantity 字段都是集合,且长度相同。这些附加字段也可能为空。
主子表采用附表机制存储,也具备前面讲到的性能优势。只是附加字段的取值是个集合,所以基表引用附表字段的方式会有所不同。
不过,同维表和主子表的附表存储方式也有坏处。因为存储方案变得更复杂了,在引用附加字段时会有不少额外的判断。
通常在主键及关联较为复杂时,使用附表方案就会有更大的性能优势。比如客户表和联系表的主键字段有多个,或订单 - 明细关联的 1:N 比例中 N 更大,这就意味着直接用普通表做常规关联时,会有更多的对比计算。
如果是简单的单一主键一对一关联,附表机制性能优势会不太明显,甚至可能会有劣势。因此,要根据具体的场景因地制宜地分析,是否采用附表机制。
理论上,主子表建立的附表还可以再有附表,但不太常见了。
SPL 在组表上实现了附表功能,需要在创建组表时指定附加字段。以订单 - 明细 - 付款表为例,代码是下面这样:
A |
B |
|
1 |
=file("orders.ctx").open().cursor() |
|
2 |
=file("details.ctx").open().cursor() |
=file("payments.ctx").open().cursor() |
3 |
=file("order_detail.ctx").create(#oid,cid,odate) |
|
4 |
=A3.attach(detail,#seq,price,quantity) |
=A3.attach(payment,#seq,amount,pdate) |
5 |
=A3.append@i(A1) |
|
6 |
=A4.append@i(A2) |
=B4.append@i(B2) |
A3 创建一个常规的组表,A4 在 A3 附加一个附表,要指定附表的名字 detail 及字段。
因为是主子表,所以子表 detail 还要设计自己的主键 seq。而 oid 是主子表共同的主键,这里不需要再写了。
同样 B4 继续在 A3 上附加了附表 payment。
A5、A6、B6 中和普通数据表一样追加数据,SPL 会使用附表的主键把记录附加到正确的主表记录上。
需要注意,基表和附表除了主键外,不能有同名的字段,否则会发生混淆。
创建好有附表的组表后,就可以在运算中引用附表的字段了。SPL 代码是这样的:
A |
|
1 |
=file("order_detail.ctx").open() |
2 |
=A1.cursor(odate,detail.sum(quantity):quantity,payment.sum(amount):amount) |
3 |
=A2.groups(odate;sum(quantity),sum(amount)) |
也可以再还原出子表的记录:
A |
|
1 |
=file("order_detail.ctx").open() |
2 |
=A1.cursor(odate,detail{price,quantity}:amount) |
3 |
=A2.run(amount=amount.sum(price*quantity)) |
4 |
=A3.groups(odate;sum(amount)) |
因为子表记录是多个,还原之后将作为主表游标的一个字段,其取值是一个序表。生成序表等动作比较复杂,这样做会损失性能,有可能抵消减少关联带来的优势。
还可以从附表来引用基表字段:
A |
|
1 |
=file("order_detail.ctx").open().attch(detail) |
2 |
=A1.cursor(odate,price,quantity) |
3 |
=A2.groups(odate;sum(price*quantity)) |
引用基表字段时直接写就可以了,性能会比上面那种生成序列字段的方法更好。
这些运算都可以支持多路游标。
英文版