txt 插入特定列_数据系统 - 面向列的存储

如果在你的fact表中有数以亿计的行和petabytes拍字节的数据,那么高效的存储和查询它们就会成为一个很有挑战性的问题。维度表通常要小的多(数百万行),所以接下来我们主要关注fact表的存储。

虽然fact表通常有超过100列宽,一般的数据仓库的查询一次只能获取它们中的4列或5列。下下图作为示例,它访问了很多的行(在2013年间某人每次买水果或者糖果的事件),但是它只需要访问fact_sales表中的data_key列、product_sk列、以及quantity列。其他列查询都忽略了。

d5bc39f14103f1ee5af80dc407d3403a.png
e46ef092ace34c5ee0228f10840181c7.png

我们应该怎样高效的执行这个查询呢?

在大多数的OLTP数据库中,存储是布局是面向行的:表中的每一行的所有数据都是一行接一行的。文档数据库也是类似的:一般一整个文档存储在一个连续的字节序列中,如下图:

b27627587fbde98772db178b84780f6b.png

为了处理像上面这样的查询,你也许会有fact_sales.data_key 和/或 fact_sals.product_sk的索引,以便存储引擎查找到对于指定日期或指定产品的所有销售记录。但是,一个面向行的存储引擎仍然需要从磁盘上加载所有的这些行(每行有100多个的属性)到内存中,粘帖并且过滤掉那些不满足条件的内容。这会花很长的时间。

面向列存储背后的想法很简单:不要将一行中的所有值都存储在一起,而是将每一列中的所有值存储在一起。如果每一列存储在一个单独的文件中,那么一次查询只需要读取和粘贴在查询中用到的列,这会节省很多的工作。如下图所示:

e30550a4864cc7830b8457743fe6f0a4.png

面向列的存储布局依赖于包含以相同顺序排列的行的每个列文件。因此,如果需要重组整个行,则可以从每个单独的列文件中获取第23个条目,并将它们放在一起以形成表的第23行。

列压缩

除了只加载查询中要求的那些列外,我们还可以通过压缩数据来降低对磁盘吞吐量的要求。幸运的是,面向列的存储通常非常适合压缩。

让我们来看一下上图中每一列的一串数值:它们通常看起来是相当重复的,这是一个很好的压缩信号。根据于列中的数据,我们可以使用不同的压缩技术。在数据仓库中一个特别有效的技术就是bitmap encoding,如下图所示:

0f518f7ad236705558d8838a2b776195.png

图1

6a5cabf34a5fcbbefeeee2023d218be4.png

图2

通常,与行相比,列中的不同值的数量较小(例如,零售商可能进行数十亿次销售交易,但只有100,000种不同的产品)。现在我们可以有n个不同值的列,把它变为n个不同的bitmap,一个bitmap对应一个不同的值,每一行中,如果有对应的值则bit置1,否则置0。

如果n很小(比如,一个coutry列中大约有200不同的值),这些bitmap,可以按照上面的图1存储,但如果n很大,那么在bitmap里会有很多的0(我们称他们是稀疏的)。这种情形下,我们可以根据上述图2可以额外的以run-length编码。这可以使得列的编码非常紧凑。

诸如此类的bitmap索引非常适合数据仓库中常见的查询类型。例如:

  • WHERE product_sk IN (30, 68, 69): 加载product_sk分别是30, 68, 69的bitmap,然后把这三个位图做位或,这样是相当高效的。
  • WHERE proddyct_sk = 31 AND store_sk = 3: 加载product_sk是31和store_sk是3的bitmap,然后按照未与计算,这是可行的,因为,包含行的每一列的顺序都是相同的,所以一个列的bitmap中的第k个bi对应着同一行中的另外一列的bitmap的第k个bit。

也有其他对于不同数据类型的压缩技术,这里我们就不细述了。可以参考如下书籍:

06852e14f6dd83d717032d965a2f34bd.png

内存带宽和矢量化处理

对于数据仓库的需要扫描数百万行的查询来说,一个很大的瓶颈就是从磁盘获取数据到内存的带宽问题。然而,这不是唯一的瓶颈。分析数据库的开发人员还担心有效利用从主内存到CPU缓存的带宽,避免在CPU指令处理管道中出现分支错误预测和气泡,以及在现代CPU中使用单指令多数据(SIMD)指令。

除了降低需要从磁盘中装载的数据的大小之外,面向列的存储布局也能很好的高效使用CPU时钟周期。例如,查询引擎可以获取适合于CPU的L1缓存的大块压缩列数据,并在紧密的循环中循环访问(即不调用函数)。 CPU可以比需要大量函数调用和要处理的每个记录的条件的代码快得多地执行这样的循环。列压缩允许列中的更多行适合相同数量的L1缓存。可以将运算符(例如前面所述的按位AND和OR)设计为直接对压缩列数据的此类块进行操作。这种技术称为矢量化处理

列存储中的排序

在一个列存储中,行是怎么排序的并不是十分重要。按照插入顺序存储它们是最简单的,因为插入新行意味着将附加到每个列文件中。但是,我们可以像以前对SSTables一样选择施加顺序,并将其用作索引机制。

请注意,对每个列进行独立排序没有任何意义,因为那样我们将不再知道列中的哪些项属于同一行。我们只能重构行,因为我们知道一个列中的第k个项目与另一列中的第k个项目属于同一行。

而且,数据需要一次性以整行排序,即使按照列排序。数据库管理员根据他们对于一般查询情况的了解,挑选表中需要排序的列。例如,如果查询通常以日期范围为目标,例如上个月,则可以将date_key设为第一个排序键。然后查询优化器只能扫描上个月的行,这比扫描所有行要快得多。

第二列可以确定第一列中具有相同值的任何行的排序顺序。例如,如果date_key是上图中的第一个排序键,则product_sk作为第二个排序键可能有意义,因此同一天同一产品的所有销售额都在存储中分组在一起。这将有助于需要在特定日期范围内按产品对销售进行分组或过滤的查询。

排序顺序的另一个优点是它可以帮助压缩列。如果主排序列没有很多不同的值,那么在排序之后,它将具有较长的序列,其中同一值连续重复很多次。一个简单的run-length编码,就像我们在之前图中使用的bitmap一样,可以将该列压缩到几千字节,即使表有数十亿行。

压缩效果在第一个排序键上最强。第二和第三排序键会更加混乱,因此不会有如此长的重复值。排序优先级之后的列实际上以随机顺序出现,因此它们也可能不会压缩。但是对前几列进行排序仍然是一个整体胜利。

几种不同的排序

这个想法的巧妙扩展是在C-Store中引入,并在商业数据仓库Vertica中采用。不同的查询受益于不同的排序顺序,那么为什么不把相同的数据以不同方式排序后再存储呢?无论如何数据需要将数据复制到多台计算机上,这样一台计算机出现故障时就不会丢失数据。您也可以存储以不同方式排序的冗余数据,以便在处理查询时,可以使用最适合查询模式的版本。

在面向列存储中有多种排列顺序有点像在面向行的存储中有多个二级索引。但很大的不同是面向行的存储把每一行放在同一个对方(在一个堆文件或一个聚合索引中),并且二级索引包含了匹配行的指针。并且在一个列存储中,一般没有任何指向数据的指针,只有包含数值的列。

对面向列存储的写入

上述优化在数据仓库中很有意义,因为大多数负载都由分析师运行的大型只读查询组成。面向列的存储,压缩和排序都有助于使这些读取查询更快。但是,它们具有使写入更加困难的缺点。

对于压缩列,无法像B树一样使用就地更新方法。如果要在已排序表的中间插入一行,则很可能必须重写所有列文件。由于通过行在列中的位置来标识行,因此插入操作必须一致地更新所有列。

幸运的是,在之前的文章我们已经看到了一个很好的解决方案:LSM树。所有写操作首先进入内存存储,然后将它们添加到已排序的结构中,并准备写入磁盘。内存存储是面向行还是面向列都没有关系。累积足够的写入后,它们将与磁盘上的列文件合并并批量写入新文件。这基本上就是Vertica所做的。

查询需要同时检查磁盘上的列数据和最近在内存中的写入,并将两者结合起来。但是,查询优化器向用户隐藏了这种区别。从分析师的角度来看,已使用插入、更新或是删除等操作修改过的数据会立即反映在之后的查询中。

聚合:多维数据集和物化视图

并非每个数据仓库都一定是列存储:还有使用了传统的面向行的数据库和其他一些体系结构。但是,用于临时分析查询的列式存储可以显着提高速度,因此它正在迅速普及。

数据仓库的另一个值得一提的方面是物化聚合。如前所述,数据仓库查询通常涉及聚合函数,例如SQL中的COUNT,SUM,AVG,MIN或MAX。如果许多不同的查询使用相同的聚合,则每次都要处理原始数据可能很浪费。为什么不缓存最常用的一些计数或总和的查询呢?

创建这种缓存的一种方法是物化视图materialized view。在关系数据模型中,通常将其定义为标准(虚拟)视图:类似于表,其内容是某些查询的结果。区别在于,物化视图是查询结果的实际副本,已写入磁盘,而虚拟视图只是编写查询的快捷方式。当您从虚拟视图中读取时,SQL引擎会即时将其扩展到视图的基础查询中,然后处理扩展的查询。

当基础数据更改时,需要更新物化视图,因为它是数据的非规范化副本。数据库可以自动执行此操作,但是这样的更新会使写入开销更大,这就是为什么在OLTP数据库中不经常使用物化视图的原因。在读取大量数据的仓库中,它们可能更有意义(它们是否真正提高读取性能取决于具体情况)。

物化视图的常见特殊情况称为数据多维数据集或OLAP多维数据集。它是按不同维度分组的聚合网格。下图显示了一个示例。

8399fe36d20dc28abd5edab36df130e2.png

现在想象一下,每个fact都只有两个维度表的外键-在上图中,它们是日期和乘积。现在,您可以绘制一个二维表,其中一个日期沿一个轴,而产品沿另一个轴。每个单元格都包含具有该日期-产品组合的所有事实的属性(例如net_price)的汇总(例如SUM)。然后,您可以沿每一行或每一列应用相同的汇总,并获得已减少了一个维度的摘要(不考虑日期的按产品销售额,或不考虑产品的按日期销售额)。

通常,事实通常具有两个以上的维度。在最上面的图中中,有五个维度:日期,产品,商店,促销和客户。很难想象一个五维超立方体将是什么样,但是原理仍然是相同的:每个单元格包含特定日期,产品,商店,促销,客户组合的销售额。然后,可以沿每个维度重复汇总这些值。

物化数据立方体的优点是某些查询变得非常快,因为它们已经被有效地预先计算了。例如,如果您想知道昨天每家商店的总销售额,则只需要查看沿适当维度的总计即可-无需扫描数百万行。

缺点是多维数据集不具有查询原始数据的灵活性。例如,由于价格不是维度之一,因此无法计算来自价格超过100美元的商品的销售比例。因此,大多数数据仓库都尝试保留尽可能多的原始数据,并仅使用聚合(例如数据多维数据集)作为某些查询的性能提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值