【性能优化】3.4 [外存查找] 行存和带值索引
3.4 行存和带值索引
上一章讲过,列式存储是提高性能的常用手段。然而,对于大多数查找任务来讲,列存却会导致更差的性能。
即使已经有序存储了,通常的列存也可以认为就无法执行不依赖于索引的二分法,这个道理和前面说的列存分段的困难是一样的,不能保证各列的同步性。使用倍增分段机制的列存可以执行二分法了(组表就是这么做的),但也要到每个字段的数据块分别去读取数据,性能不会很好。
列存数据表上也可以建立索引来避免遍历,但非常麻烦。理论上讲,要在索引中把各个字段的物理位置都记录下来,索引容量就会比行存时的索引大很多,甚至可能和原数据表一样大(因为每个字段都有个物理位置,索引中的数据量和原数据相同,仅是数据类型简单)。而且,读取时也要分别到各个字段的数据区去读,而硬盘有个最小读取单位,这会导致各列的总读取量远远超过行存时候,表现出来就是查找性能差很多。
列存更适合数据的遍历。需要高性能查找的场景,尽量不要使用列式存储。
SPL 因为采用了倍增分段机制之后,可以较迅速地按记录序号在列存格式中找到各字段值,所以 SPL 在索引中事实上保存的并不是各个字段值的物理位置,而是整条记录的序号。这样索引的容量就能小得多,和行存时差不多。但仍然需要到各个字段的数据块分别读取,性能还是赶不上行存上的索引。
SPL 的组表缺省会使用列存,但也提供行存模式,可以在创建时用选项 @r 指明:
A |
B |
|
1 |
=file("data.ctx").create@r(ID,…) |
|
… |
… |
对于遍历和查找都要求很高的场景,就只能用空间换时间了。数据冗余存储两遍,列存用于遍历,行存用于查找。
SPL 还提供了一种带值索引,在建立索引时可以把其它字段值一起复制过来,原组表可以继续采用列存用于遍历,而索引本身已经保存了字段值并使用行存,在查找时一般不再访问原组表,能获得更好的性能,但也会占用更大的存储空间。
A |
|
1 |
=file("data.ctx").open() |
2 |
=file("data.idx") |
3 |
=A1.index(A2;ID;…) |
4 |
=A1.icursor(…;ID==123456;A2).fetch() |
5 |
=A1.icursor(…;ID>=123456 && ID<=654321;A2) |
A2 建立索引时将要引用的字段抄在参数的…部分,就可以在索引中复制这些字段值,以后取出目标值时,只要涉及字段在这部分内,就不必再读取原组表。读者可以自行对比带值索引和普通索引的性能以及空间差异。