列式存储和数据压缩,对于一款高性能数据库来说是必不可少的特性。一个非常流行的观点认为,如果你想让查询变得更快,最简单且有效的方法是减少数据扫描范围和数据传输时的大小,而列式存储和数据压缩就可以帮助我们实现上述两点。列式存储和数据压缩通常是伴生的,因为一般来说列式存储是数据压缩的前提。
按列式存储与按行存储相比,前者可以有效减少查询时所需扫描的数据量,这一点可以用一个示例简单说明。假设一张数据表 A
拥有 50
个字段 A1~A50
,以及 100
行数据。现在需要查询前 5
个字段并进行数据分析,则可以用如下 SQL
实现:
$ SELECT A1, A2, A3, A4, A5 FROM A;点击复制复制失败已复制
如果数据按行存储,数据库首先会逐行扫描,并获取每行数据的所有 50
个字段,再从每一行数据中返回 A1~A5
这 5
个字段。不难发现,尽管只需要前面的 5
个字段,但由于数据是按行进行组织的,实际上还是扫描了所有的字段。如果数据按列存储,就不会发生这样的问题。由于数据按列组织,数据库可以直接获取 A1~A5
这 5
列的数据,从而避免了多余的数据扫描。
按列存储相比按行存储的另一个优势是对数据压缩的友好性。同样可以用一个示例简单说明压缩的本质是什么。假设有两个字符串 abcdefghi
和 bcdefghi
,现在对他们进行压缩,如下所示:
- 压缩前:
abcdefghi_bcdefghi
- 压缩后:
abcdefghi_(9, 8)
可以看到,压缩的本质是按照一定步长对数据进行匹配扫描,当发现重复部分的时候就进行编码转换。例如上述示例中的 (9, 8)
,表示从下划线开始向前移动 9
个字节,会匹配到 8
个字节长度的重复项,即这里的 bcdefghi
。
真实的压缩算法自然比这个示例更为复杂,但压缩的实质就是如此。数据中的重复项越多,则压缩率越高;压缩率越高,则数据体量越小;数据体量越小,则数据在网络中传输越快,对网络带宽和磁盘 IO
的压力也就越小。既然如此,那怎样的数据最可能具备重复的特性呢?答案是属于同一列字段的数据,因为他们拥有相同的数据类型和现实语义,重复项的可能性自然就更高。
ClickHouse
就是一款使用列式存储的数据库,数据按列进行组织,属于同一列的数据会被保存在一起,列与列之间也会由不同的文件分别保存(这里主要至 mergeTree
表引擎)。数据默认使用 LZ4
算法压缩,在 Yandex.Metrica
的生产环境中,数据总体的压缩比可以达到 8:1
(未压缩前 17PB
,压缩后 2PB
)。列式存储除了降低 IO
和存储的压力之外,还为向量化执行做好了铺垫。