1.2.1 深度列存储
在数据分析场景中,如果我们的圈选查询底表数据量有数万亿行、PB级别,那么如何高效存储和实时地查询分析这些数据将是一个颇具挑战的问题。圈选底表的列数通常是几百列,甚至可能达到上千列,但我们知道,典型的查询分析场景一次往往只访问其中的3~7列。如何设计一个存储结构,以实现高效地执行这个查询?我们来详细分析一下。
在大多数OLTP数据库中,存储都是面向行的:一张表中的一行数据都是彼此相邻存储的。文档数据库亦类似,整个文档被存储为一个连续的字节序列。如果用面向行的存储,需要将所有行从磁盘加载到内存中,解析、过滤出目标行,但这可能需要很长时间,也可能导致内存溢出(Out Of Memory,OOM)等问题。这个时候,面向列存储(以下简称列存储)的解决方案就自然被人想到了。
列存储的想法很简单:不是将每一行中的所有值存储在一起,而是将每一列中的值存储在一起,此时查询分析就只需要读取和解析需要的列,从而节省大量工作。
为了更清晰地展示列存储的概念,先引入一张逻辑表结构,如表1-2所示。
表1-2 一张逻辑表结构
前文提到,在大多数OLTP系统中,数据的物理存储是以面向行的方式组织的,即行存储,如表1-3所示。
表1-3 行存储数据结构
使用这种方式可以把一行数据存放在一起。用具体的数据结构来描述行存储的代码示例如下:
而在OLAP系统中,一般是查询大量行数据中的特定某几列,然后执行max、min、count、sum、avg等聚合函数进行统计分析。相比行存储把一行数据相邻存放,面向列存储是把一列的数据存放在一起——这就是列存储的思想,如表1-4所示。
表1-4 列存储数据结构
用数据结构模型来描述行存储的示例如下:
这样做的好处是显而易见的。统计分析通常是在某个字段上进行sum、count、max、min、avg、group by,列存储可以大大减少数据扫描I/O。另外,相同列的数据类型通常相同,可以获得更优的压缩率,大大减少数据存储。针对分析类查询,通常只需要读取表的一小部分列。在列式数据库中你可以只读取需要的数据列。例如,如果只需要读取100列中的5列,使用列存储可以帮助你将I/O消耗降低为原消耗的1/20。数据按列存储更容易压缩,这进一步降低了I/O的体积,从而可以使更多的数据被系统缓存。如果你想让查询变得更快,可以使用以下两种方法:
❑缩小数据扫描范围;
❑减少数据传输大小。
而列存储和数据压缩正是为此而来。列存储和数据压缩通常是伴生的,列存储是数据压缩的前提。列存储的好处简单总结如下:
❑将I/O限制为实际需要的数据,仅加载需要访问的列。
❑节省空间,列存储更容易压缩。
❑便于压缩编码。
❑适用于向量化执行引擎。
最后,给出一张行存储与列存储优缺点对比的表格清单,如表1-5所示。
表1-5 行存储与列存储优缺点对比
毫无疑问,行存储和列存储各有优缺点,在实际应用时需要结合具体的问题场景进行选择。
小贴士:Parquet列存储格式
Apache Parquet(https://parquet.apache.org/)是一种列存储格式。Parquet实现了非常有效的压缩和编码方案,提供了高效的列式数据存储表达能力,被广泛应用于Hadoop生态系统中。Parquet文件存储模型如图1-7所示。
图1-7 Parquet文件存储模型
图1-7展示了Parquet文件的物理数据组织结构(行组、列块、页和编码值)。从层次上讲,一个文件由一个或多个行组组成。一个行组中每列只包含一个列块,一个列块包含一页或多页。
Parquet的各组件说明如表1-6所示。
表1-6 Parquet组件说明
图1-7中的Parquet文件格式可以用如下文本描述:
这个表有N列,分成M行组。文件元数据包含所有列的元数据的开始位置。
Parquet文件存储格式与数据处理框架、数据模型、编程语言无关。Parquet文件存储格式不显式支持16位整型,而使用具有高效编码的32位整型。这使得Parquet文件的读写更简单。Parquet存储支持的6种类型如下。
❑BOOLEAN:1位布尔值。
❑INT32:32位有符号整数。
❑INT64:64位有符号整数。
❑INT96:96位有符号整数。
❑FLOAT:IEEE 32位浮点值。
❑DOUBLE:IEEE 64位浮点值。
❑BYTE_ARRAY:任意长的字节数组。
小贴士:ClickHouse支持Parquet格式的导出和导入
将ClickHouse数据导出的Parquet格式的方法如下:
将Parquet文件数据导入ClickHouse的方法如下:
注意:ClickHouse表的列名必须与Parquet表的列名一致。
ClickHouse表的列数据类型可以不同于导入的Parquet数据类型。在导入数据时,ClickHouse会根据下面表格中的类型映射,把Parquet中的数据类型转换成ClickHouse表中的列对应的数据类型。
Parquet和ClickHouse的数据类型的映射关系如表1-7所示。
表1-7 Parquet和ClickHouse的数据类型的映射关系
(续)
注意,ClickHouse不支持的Parquet数据类型包括DATE32、TIME32、FIXED_SIZE_BINARY、JSON、UUID、ENUM。