【性能优化】2.7 [外存数据集] 数据更新

 

【性能优化】2.6 [外存数据集] 复组表

2.7 数据更新

前面只讨论了外存数据表中如何追加数据,而没有涉及如何修改。

我们一直在努力把数据尽量紧凑且连续存储,目的是为了减少硬盘存储量以减少读取时间。而紧凑存储后就无法直接实现数据更新了,修改的数据未必能够填满原数据的空位(因为有各种压缩编码,同一类型的数据都不一定占一样字节数)。如果填不下就不能继续存储在原来位置,填不满也会造成空洞,都会导致数据不再连续紧凑,插入和删除则更是如此。列存还会加剧这个问题,所有字段的数据块都面临这种窘境。

为了适应经常修改需求的 OLTP 数据库,通常会使用不压缩的行存方式,就不适合用于以读取计算为主的 OLAP 业务。而且,即使这样,也仍然可能在多次写入后产生很多不连续的数据块,导致访问性能严重下降。

数据可修改和高性能运算是一对矛盾,巨大数据量运算和允许频繁大量修改是不可能都同时高性能实现的。

本书主要讨论高性能计算,所以将牺牲掉频繁大量修改的需求。

文本文件和集文件都可以认为不支持修改(修改意味着全部重写一遍),组表可以支持少量修改。

当修改量不大时,SPL 将修改(也包括插入、删除)的数据写到一个单独的数据区,称为补区。在读取时补区与正常数据一起归并计算,这样访问时可以感觉不到补区的存在。而且,要保证较高性能的归并处理,补区的数据量就不能太大。

要修改必须有主键,SPL 要求组表的主键必须有序(所以才会有插入动作,关系数据库不区分插入和追加)。


A

B

1

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

2

=10.new(rand(1000):ID,…)

>A1.update(A2)

3

=10.new(rand(10000):ID,…)

>A1.delete(A3)

因为不允许太多修改,SPL 只提供了使用内存排列作为参数来执行修改和删除动作,而不是像追加时可以针对游标进行。修改时如果发现主键值在组表中不存在,则会插入。

经过多次修改后,补区也会越来越大,对性能的影响也会更大,需要定期对补区进行清理,将其合并进原数据区实现紧凑连续存储并清除补区。


A

B

1

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

2

if (day(now())==1

>A1.reset()

reset 将把补区混入正常存储区,重写部分数据(从补区中最小的主键值开始,之前的数据不用改变)以保持整个组表的紧凑连续存储。

OLAP 业务使用的历史数据一般不会有大量频繁的更新动作,但有时历史数据的大批删除却是必要的。年代久远的数据已经失去查询分析的意义,如果继续存放在数据表中,即占用大量空间,也会影响到查询性能。但是,我们要求数据连续存储,即使本身是按时间有序的数据,要把最初时段的数据删除,也会导致所有数据要全部重写一遍,这非常耗时。

SPL 提供了复组表,允许由多个文件构成一个组表,这些文件称为复组表的分区


A

B

1

=file("data.ctx":to(12)).create(#dt,…;month(dt))

2

=file("data.ctx":[5,6,7,8,9,10,11,12]).open()

创建组表时,可以设置这个组表由多个物理文件构成,每个文件会有个编号,称为分区号,即 A1 中参数的 to(12) 部分,说明组表会分成分区号为 1,2,…,12 的 12 个文件。同时给出一个从数据计算分区号的表达式,这里是 month(dt)。使用选项 append@x 追加数据时,SPL 会针对每条追加记录计算 month(dt),根据结果把数据追加到相应分区号的文件中。

使用时,可以只选择其中部分分区拼成一个逻辑组表工作,这些分区号可以不连续,但必须有序。早期数据要删除时只要把相应分区的文件删除,再打开组表时不再把这个分区加入到列表中即可,不需要重写其它分区中的数据。

类似地,也可以将上节中讲到的历史数据文件和近期数据文件分别存储成复组表的两个分区(历史数据如果量很大也可以存成多个分区),这样就可以将多个文件在逻辑上作为一个组表来访问,代码会相对简洁。

更大数据量上的更新也可以使用复组表实现实时处理,和前面介绍的实时追加方法类似,将数据分成多个层次的分表,然后在实时更新的过程中同时逐层归并。更新的处理会比单纯追加更复杂一些,特别是有删除情况时,具体细节也可以参考 SPL 论坛上的相关例程。

【性能优化】3.1 [外存查找] 二分法
【性能优化】 前言及目录