列存储
行存储与列存储
传统的关系数据库如

如果事实表中有万亿行和数SELECT *
查询很少用于分析
SELECT
dim_date.weekday,
dim_product.category,
SUM(fact_sales.quantity) AS quantity_sold
FROM fact_sales
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
WHERE
dim_date.year = 2013 AND
dim_product.category IN ('Fresh fruit', 'Candy')
GROUP BY
dim_date.weekday, dim_product.category;
在大多数
面向列的存储背后的想法很简单:不要将所有来自一行的值存储在一起,而是将来自每一列的所有值存储在一起。如果每个列存储在一个单独的文件中,查询只需要读取和解析查询中使用的那些列,这可以节省大量的工作。

行存储与列存储比较
行存储,顾名思义即是将数据是按行存储,行存储的写入是一次性完成,消耗的时间比列存储少,并且能够保证数据的完整性;其插入与更新操作更为容易。但是其缺陷在于建立索引需要花费大量时间和资源,没有索引的话查询会产生大量的
列存储,数据按列存储,每一列单独存放,数据即是索引。只查询涉及的列,会大量降低系统
行式存储 | 列式存储 | |
---|---|---|
优点 | 数据被保存在一起, |
查询时只有涉及到的列会被读取,投影 |
缺点 | 选择 |
选择完成时,被选择的列要重新组装, |
场景 | 适合数据存储写入、更新较多的场景,比如 |
不适合数据频繁写入、更新的场景,主要适合频繁查询的场景,大数据环境下优势更明显,比如分布式实时查询 |
从写方面来看,行存储的写入是一次完成,也就是说行存储各字段写入是在一次
另外由于列存储的每一列数据类型是同质的,不存在二义性问题,比如说某列数据类型为整型(int
Wide Column | 宽表

关系型数据库中往往是二维模型,并且包含了固定的
-
Primary Key & Partition Key: 每行都有一个主键,由多个(1-4)列组成。主键使用固定模式定义,主要用于唯一标识一行数据。主键的第一列称为分区键,用于对表进行分区。每个分区都分配给不同的机器进行维护。在同一分区键中,提供了跨行事务。 -
Attribute column: 除行中的主键列之外的所有列都是属性列。属性列对应于多个值,不同的值对应于不同的版本,行可以存储无限数量的属性列。
我们还可以针对
-
生存时间:可以为每个表定义生存时间。例如,如果将生存时间配置为一个月,则将自动清除在一个月之前写入表数据的数据。数据的写入时间由版本确定,版本通常由数据写入服务器端的服务器时间确定,也可以由应用程序指定。
-
最大版本号:可以为每个表定义每列中保存的最大版本数,用于控制列中的版本数;系统会自动清除超过最大版本数的数据。
列压缩
除了仅从磁盘加载查询所需的列以外,我们还可以通过压缩数据来进一步降低对磁盘吞吐量的需求。幸运的是,面向列的存储通常很适合压缩。它们通常看起来是相当重复的,这是压缩的好兆头。根据列中的数据,可以使用不同的压缩技术。在数据仓库中特别有效的一种技术是位图编码,如下图所示:

通常情况下,一列中不同值的数量与行数相比较小(例如,零售商可能有数十亿的销售交易,但只有
如果
这些位图索引非常适合数据仓库中常见的各种查询。例如:
WHERE product_sk IN(30,68,69)
加载product_sk = 30
product_sk = 68
product_sk = 69
WHERE product_sk = 31 AND store_sk = 3
加载 product_sk = 31
和 store_sk = 3
的位图,并逐位计算
对于不同种类的数据,也有各种不同的压缩方案,譬如
内存带宽和向量处理
对于需要扫描数百万行的数据仓库查询来说,一个巨大的瓶颈是从磁盘获取数据到内存的带宽。但是,这不是唯一的瓶颈。分析数据库的开发人员也担心有效利用主存储器带宽到
除了减少需要从磁盘加载的数据量以外,面向列的存储布局也可以有效利用
Spark MaxCompute
这里以

在查询过程中,查询引擎仅去字典表里找到字符串对应数字

列存储中的排序顺序
在列存储中,存储行的顺序并不一定很重要。按插入顺序存储它们是最简单的,因为插入一个新行就意味着附加到每个列文件。但是,我们可以选择强制执行一个命令,就像我们之前对
注意,每列独自排序是没有意义的,因为那样我们就不会知道列中的哪些项属于同一行。我们只能重建一行,因为我们知道一列中的第
相反,即使按列存储数据,也需要一次对整行进行排序。数据库的管理员可以使用他们对常见查询的知识来选择表格应该被排序的列。例如,如果查询通常以日期范围为目标,例如上个月,则可以将
第二列可以确定第一列中具有相同值的任何行的排序顺序。例如,如果
排序顺序的另一个好处是它可以帮助压缩列。如果主要排序列没有多个不同的值,那么在排序之后,它将具有很长的序列,其中相同的值连续重复多次。一个简单的运行长度编码可以将该列压缩到几千字节:即使表中有数十亿行。
第一个排序键的压缩效果最强。第二和第三个排序键会更混乱,因此不会有这么长时间的重复值。排序优先级下面的列以基本上随机的顺序出现,所以它们可能不会被压缩。但前几列排序仍然是一个整体。
几个不同的排序顺序
这个想法的巧妙扩展在
在一个面向列的存储中有多个排序顺序有点类似于在一个面向行的存储中有多个二级索引。但最大的区别在于面向行的存储将每一行保存在一个地方(在堆文件或聚簇索引中
写入列存储
这些优化在数据仓库中是有意义的,因为大多数负载由分析人员运行的大型只读查询组成。面向列的存储,压缩和排序都有助于更快地读取这些查询。然而,他们有写更加困难的缺点。
使用
幸运的是,本章前面已经看到了一个很好的解决方案:
查询需要检查磁盘上的列数据和最近在内存中的写入,并将两者结合起来。但是,查询优化器隐藏了用户的这个区别。从分析师的角度来看,通过插入,更新或删除操作进行修改的数据会立即反映在后续查询中。