【性能优化】2.7 [外存数据集] 数据更新
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 论坛上的相关例程。
=file(“data.ctx”).open()
if (day(now())==1 >A1.reset@q()
A1.reset@q ()报错了,A1单元格不需要用到open(),否则报错类型不匹配;
参考文档:http://d.raqsoft.com.cn:6999/esproc/func/reset.html#__343
大佬们,下午好!
1、关于复组表的分区号,只能是整数数列吗?比如本文中最后的代码格中的 to(12) 部分,如果是按月份分区,那 1,2,3…12 这样的编号分别代表一个月,已经有足够的辨识度。
如果按地区分区 (或者其它非数列),比如有 12 个地区,也是用自然数分区,那 1 代表哪个地区,3 代表哪个地区,这样的话,辨识度就没那么清晰了。
分区号可不可以支持文本序列,数列肯定简单方便,文本序列会比较容易辨识,比如,可不可以这样写:
file(“data.ctx”:[“华东”,“华南”,“西南”…]).create@y (字段,…;fx(操作某地区字段))
然后会生成对应文件: 华东.data.ctx,华南.data.ctx,西南.data.ctx…。
不知道这样纯数列的设计是不是还有其他考虑?
2、本文倒数第二段,有这么一句话:“使用时,可以只选择其中部分分区拼成一个逻辑组表工作,这些分区号可以不连续,但必须有序。”
分区号可以不连续,这个好理解。"必须有序" 的意思是必须写成 [1,3,6,8] 这样的升序形式,还是 [8,6,3,1] 这样降序也行?升序和降序都是有序。
我试了一下,乱序也能读出来,比如 file(“data.ctx”:[8,1,6,3]).open(),没有报错。当然,有序肯定是最好的,千方百计要弄成有序。
恳请大佬们得闲时给予指导解惑🙏 谢谢!
字符串比整数存储传输复杂得多,这东西本来是为了性能目标的,不能再搞复杂了。
程序不会被检查次序对不对,这时候出什么后果自己负责就行。这种情况很多,比如设置主键时也不检查是不是重复了。
SPL 解释器的开发原则是这样的:正确输入能得到正确结果,不正确输入的结果无定义,只要不死机就行。
谢谢老贼回复🙏
"必须有序" 这词表达的很坚决,要不是看您这篇文章,关于组表的这些细节也不会去关注到。
还是要多看、细看大神的文章,每次看都会有不一样的收获,哈哈😄
Thx again!