简单谈谈OLTP,OLAP和列存储的概念
- OLTP(online transaction processing)
- OLAP(online analytic processing)
- OLTP VS OLAP
- 数据仓库
- OLTP数据库和数据仓库之间的差异
- 星型和雪花分析模式
- 列式存储
- 列压缩
- 列式存储和列族
- 内存带宽与矢量化处理
- 列式存储中的排序顺序
- 几个不同的排序顺序
- 写入列式存储
- 聚合:数据立方体和物化视图
- 小结
整理至: 数据密集型应用第三章后半部分
OLTP(online transaction processing)
在商业数据处理的早期阶段,写入数据库通常对应于商业的交易场景,如: 销售,订单等涉及金钱交易的场景,交易的英文为transaction,也就是事务一词的来源,在计算机领域代表一个逻辑单元的一组读写操作。
虽然数据库现在广泛应用于各种场景,但是应用程序通常还是使用索引中的某些键来查询少量的记录,或者根据用户的输入插入或者更新记录,因为这些应用程序是交互式的,所以访问模式也被称为在线事务处理(OLTP)。
OLAP(online analytic processing)
数据库同样也开始越来越多的用于数据分析,数据分析需要扫描大量的记录,同时每个记录通常只会读取少数的几列,并计算汇总统计信息(如计数,求和或平均值),而不是返回原始数据给用户。
例如: 统计每个店铺平均销售额,这些查询通常由业务分析师编写,以形成有助于公司管理层更好的决策;为了与事务处理系统进行区分,我们称之为在线分析处理(OLAP)。
OLTP VS OLAP
属性 | 事务处理 OLTP | 分析系统 OLAP |
---|---|---|
主要读特征 | 基于键查询,每次查询返回少量记录 | 对大量记录进行汇总 |
主要写特征 | 随机访问,写入要求低延时 | 批量导入(ETL)或事件流 |
适用场景 | 终端用户,通过网络应用程序 | 内部数据分析师,为决策提供支持 |
数据表征 | 数据的最新状态(当前时间点) | 随时间推移的历史事件 |
数据规模 | GB ~ TB | TB ~ PB |
最开始的时候,相同的数据库可以同时用于事务处理和分析查询,但是后来大公司开始慢慢放弃使用OLTP系统用于数据分析,而是使用单独的数据库进行分析,这个单独的数据库也被称为数据仓库。
数据仓库
对于大型企业而言,通常会存在几十种不同的交易处理系统,由于这些OLTP系统对业务运行至关重要,所以一般不允许直接在OLTP数据库上运行临时分析查询,这些查询的代价很高,因为需要扫描大量数据集,这会损害OLTP系统并发执行事务的性能。
因此,通常的做法是使用单独的数据库,即数据仓库,数据仓库包含公司所有OLTP系统的只读副本,从OLTP数据库中周期性提取数据,转换为分析友好的模式,执行必要的清理,然后加载到数据仓库中。
将数据导入数据仓库的过程称为提取-转换-加载(Extract-Transform-Load,ETL),如下图所示:
使用单独的数据仓库而不是直接查询OLTP系统进行分析,很大的优势在于数据仓库可以针对分析访问模式进行优化。
OLTP数据库和数据仓库之间的差异
由于SQL通常适合分析查询,有许多图形化数据分析工具,它们可以生成SQL查询,可视化结果并支持分析师探索数据,例如通过向下钻取,切片和切丁等操作,所以数据库仓库最常见的模型是关系型。
注意:
向下钻取、切片和切丁都是大数据领域中常用的数据分析技术。
向下钻取(Drill Down)是指通过逐层展开数据细节,深入挖掘数据的方法。
- 例如,在一个销售数据报表中,我们可以通过向下钻取来了解某个地区的销售情况,然后再进一步了解该地区某个销售点的销售情况,以此类推,直到最细节的数据。
切片(Slice)是指根据某个维度对数据进行切割,以便更好地了解数据的分布情况。
- 例如,在一个客户满意度调查数据中,我们可以根据不同的地区对数据进行切片,以了解不同地区的客户满意度情况。
切丁(Dice)是指根据多个维度对数据进行切割,以更全面地了解数据的分布情况。
- 例如,在一个销售数据报表中,我们可以根据不同的地区和时间对数据进行切丁,以了解不同地区和时间段的销售情况。
总之,向下钻取、切片和切丁都是大数据领域中非常重要的数据分析技术,能够帮助我们更好地了解数据的分布情况和趋势,从而做出更准确的决策。
虽然,数据仓库和关系型LOTP数据库看起来相似,因为它们都具有SQL查询接口,但在内部存储和查询引擎实现上,确是完全不同的。
星型和雪花分析模式
根据不同的应用需求,事务处理领域会采用多种不同的数据模型,如: 关系型数据库,文档型数据库,图数据库等。
而对于分析型业务而言,数据模型少的多,大部分数据仓库都使用星型分析模式。
星型分析模式:
- 星型分析模式是一种数据仓库设计模式,它使用中央事实表(Fact Table)和周围的维度表(Dimension Table)来存储和分析数据。
- 中央事实表包含了所有的事实数据,而维度表则包含了与事实数据相关的维度信息。
- 这种模式的优点是能够快速地进行多维度的数据分析,但缺点是在处理大量数据时可能会出现性能问题。
这里以零售数据仓库为例:
模式的中心是一个所谓的事实表,在本例中为fact_sales表,事实表的每一行表示在特定时间发生的事件,这里每一行代表客户购买的一个商品。
如果我们分析的是网站流量而不是零售,则每一行可能代表页面视图或者用户的单击。
通常,事实会被捕获为单独的事件,这样之后的分析具有最大的灵活性,但是同样也意味着事实表会变的很大。
事实表中的列是属性,例如产品销售的价格和供应商处购买的成本,其他列可能会音乐其他表的外键,称为维度表。由于事实表中每一行代表一个事件,维度通常代表事件的对象(who),什么(what),地点(where),时间(when),方法(how)以及原因(why)。
在本例中,其中一个维度是销售的产品(dim_product),fact_sales表中每一行都使用外键来表示在该特定事务中出售的产品。
日期和时间通常使用维度来表示,这样可以对日期(如公共假期)的相关信息进行编码,从而查询可以对比假期和非假期日之间的销售情况。
名称"星型模式"来源于当表关系可视化时,事实表位于中间,被一系列维度表包围,这些表的连接就像星星的光芒。
雪花分析模式:
- 雪花分析模式也是一种数据仓库设计模式,它与星型分析模式类似,但是在维度表中使用了更多的层级关系。
- 这种模式的优点是能够更好地处理复杂的分析需求,但缺点是查询性能可能会受到影响。
雪花分析模式是星型分析模式的一个变体,在雪花分析模式中将维度进一步细分为子空间。
例如: 品牌和产品类别可能有单独的表格,dim_product表中的每一行可以再次引用品牌和类别作为外键,而不是将其作为字符串直接存储在dim_product表中。
雪花分析模式比星型分析模式更规范化,但是星型分析模式通常是首选,主要是因为对于分析人员,星型分析模式使用起来更简单。
在典型的数据仓库中,表通常会非常宽,事实表可能会有几百列,维度表也可能会很宽,可能会包括与分析相关的所有元数据。
列式存储
虽然事实表中通常超过100列,但是典型的数据仓库查询往往一次只访问其中的4或5个列。如果事实表中有PB级别大小的数据,则高效地存储和查询这些数据将成为一个难点。
维度表通常小得多,只有数百万行。
还是以下面这张图为例,我们通过编写SQL语句来分析人们是否更倾向于在一周的某一天购买新鲜水果或糖果
SELECTdim_date.weekday,dim_product.category,SUM(fact_sales.quantity) AS quantity_sold
FROM fact_salesJOIN dim_date ON fact_sales.date_key = dim_date.date_keyJOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
WHEREdim_date.year = 2013 ANDdim_product.category IN ('Fresh fruit', 'Candy')
GROUP BYdim_date.weekday, dim_product.category;
我们如何有效地执行这个查询?
- 在大多数 OLTP 数据库中,存储都是以面向行的方式进行布局的:表格的一行中的所有值都相邻存储。
- 文档数据库也是相似的:整个文档通常存储为一个连续的字节序列。
为了处理像本例中的查询,你可能在 fact_sales.date_key、fact_sales.product_sk
上有索引,它们告诉存储引擎在哪里查找特定日期或特定产品的所有销售情况。
但是,面向行的存储引擎仍然需要将所有这些行(每个包含超过 100 个属性)从硬盘加载到内存中,解析它们,并过滤掉那些不符合要求的属性。这可能需要很长时间。
列式存储背后的想法很简单:不要将所有来自一行的值存储在一起,而是将来自每一列的所有值存储在一起。
如果每个列式存储在一个单独的文件中,查询只需要读取和解析查询中使用的那些列,这可以节省大量的工作。
列式存储布局依赖于每个列文件包含相同顺序的行。 因此,如果你需要重新组装完整的行,你可以从每个单独的列文件中获取第 23 项,并将它们放在一起形成表的第 23 行。
列压缩
除了仅从硬盘加载查询所需的列以外,我们还可以通过压缩数据来进一步降低对硬盘吞吐量的需求。幸运的是,列式存储通常很适合压缩。
列压缩技术的核心思想是对相同的数据进行重复利用,通过压缩数据的方式减小存储空间。常见的列压缩技术包括字典编码、位图压缩和矩阵压缩等。
- 以字典编码为例,假设有一个包含城市名称和对应人口数量的数据表,其中城市名称存在重复。使用字典编码技术,可以将城市名称单独存储在一个字典表中,然后在原始数据表中使用字典表中的编号代替城市名称。这样可以大大减小城市名称的存储空间,并且在查询时也可以更快地进行匹配。
- 另一个例子是位图压缩技术,假设有一个包含用户ID和对应购买记录的数据表,其中购买记录只有两种状态:已购买和未购买。使用位图压缩技术,可以将已购买和未购买分别用1和0表示,然后将所有用户的购买记录按位存储在一个位图中。这样可以大大减小存储空间,并且在查询时也可以更快地进行位运算操作。
这里以位图编码为例进行介绍,如下图所示:
通常情况下,一列中不同值的数量与行数相比要小得多。
例如: 零售商可能有数十亿的销售交易,但只有 100,000 个不同的产品
现在我们可以拿一个有 n 个不同值的列,并把它转换成 n 个独立的位图:
- 每个不同值对应一个位图,每行对应一个比特位。如果该行具有该值,则该位为 1,否则为 0。
这些位图索引非常适合数据仓库中常见的各种查询。例如:
WHERE name IN("大忽悠","小朋友")
加载 name=“大忽悠” 和 name = “小朋友” 这三个位图,并计算三个位图的按位或(OR):
WHERE name="大忽悠" and school="清华"
加载 name=“大忽悠” 和 school=“清华” 的位图,并计算按位与(AND)。这是因为列按照相同的顺序包含行,因此一列的位图中的第 k 位和另一列的位图中的第 k 位对应相同的行。
如果 n 非常小(例如,国家 / 地区列可能有大约 200 个不同的值),则这些位图可以将每行存储成一个比特位。
但是,如果 n 更大,大部分位图中将会有很多的零(我们说它们是稀疏的)。在这种情况下,位图可以另外再进行游程编码(run-length encoding,一种无损数据压缩技术),如下所示。这可以使列的编码非常紧凑。
对于不同种类的数据,也有各种不同的压缩方案。
列式存储和列族
Cassandra 和 HBase 有一个列族(column families)的概念,他们从 Bigtable 继承。
然而,把它们称为列式(column-oriented)是非常具有误导性的:
- 在每个列族中,它们将一行中的所有列与行键一起存储,并且不使用列压缩。
因此,Bigtable 模型仍然主要是面向行的。
内存带宽与矢量化处理
对于需要扫描数百万行的数据仓库查询来说,一个巨大的瓶颈是从硬盘获取数据到内存的带宽。
但是,这不是唯一的瓶颈。分析型数据库的开发人员还需要有效地利用内存到 CPU 缓存的带宽,避免 CPU 指令处理流水线中的分支预测错误和闲置等待,以及在现代 CPU 上使用单指令多数据(SIMD)。
什么是分支预测错误和闲置等待:
当CPU在执行指令时,它会通过指令处理流水线一个接一个地处理指令。分支预测错误和闲置等待是在指令处理流水线中常见的问题。
分支预测错误是指在CPU处理分支指令时,CPU会尝试预测分支的结果,以便在预测正确的情况下更快地执行指令。然而,如果CPU的预测错误,那么它就必须回退并重新执行之前的指令,这会导致指令处理流水线中的延迟和性能下降。
- 例如,假设一个程序有一个循环,它在第一次迭代时会跳到循环的结尾,而在后续的迭代中会跳过循环。如果CPU预测错误,它会在后续的迭代中跳到循环的结尾,这会导致指令处理流水线中的延迟和性能下降。
处理器在执行指令时,可能会遇到一些依赖关系,需要等待前面的指令执行完毕才能继续执行。这会浪费时间和资源,造成闲置等待。
- 例如,假设一个程序需要从内存读取一些数据,然后将这些数据写回内存。如果CPU在等待内存读取完成时处于闲置状态,那么它就无法执行其他指令,这会导致指令处理流水线中的延迟和性能下降。
为了解决这些问题,CPU通常会使用一些技术,例如分支目标缓存和乱序执行,以减少分支预测错误和闲置等待的影响。
为什么分支目标缓存和乱序执行可以提高处理器的性能和效率:
- 分支目标缓存是一种缓存机制,它存储了分支语句的目标地址。当处理器遇到分支语句时,它会首先检查分支目标缓存,以确定分支的目标地址。如果目标地址已经在缓存中,处理器就可以直接跳转到目标地址,而不需要进行预测。这样可以减少分支预测错误的影响。
- 例如,假设程序中有一个if语句,根据条件跳转到不同的代码块。如果处理器每次都需要预测分支的结果,就会浪费时间和资源。但是,如果使用分支目标缓存,处理器可以直接跳转到目标地址,避免了预测错误的影响。
- 乱序执行是一种指令执行技术,它允许处理器在等待前面的指令执行完毕时,继续执行后面的指令。这样可以减少闲置等待的影响。
- 例如,假设程序中有一系列指令,指令1和指令2之间存在依赖关系,需要等待指令1执行完毕后才能执行指令2。如果处理器采用乱序执行技术,它可以在等待指令1执行的同时,先执行后面的指令3和指令4。这样可以减少等待的时间,提高处理器的效率。
- 综上所述,分支目标缓存和乱序执行是现代处理器常用的两种技术,它们可以减少分支预测错误和闲置等待的影响,提高处理器的性能和效率。
单指令多数据(SIMD)指令是什么,以及为什么它可以加速运算:
- 单指令多数据(SIMD)指令是一种计算机指令集,它可以同时对多个数据进行相同的操作。这种指令集可以在一个时钟周期内处理多个数据,从而加速计算速度。
- 在传统的计算机指令中,每个指令只能处理一个数据。如果需要对多个数据进行相同的操作,需要多次执行相同的指令。而在SIMD指令中,可以一次性处理多个数据,从而减少了指令的执行次数,提高了计算效率。
- 例如,假设需要对一个向量中的每个元素进行加法操作。在传统的指令中,需要使用循环来依次处理每个元素,而在SIMD指令中,可以一次性处理整个向量,从而大大加快了计算速度。
- 因此,SIMD指令在许多需要高性能计算的领域中得到广泛应用,例如图像处理、数字信号处理、科学计算等。
除了减少需要从硬盘加载的数据量以外,列式存储布局也可以有效利用 CPU 周期。
例如,查询引擎可以将一整块压缩好的列数据放进 CPU 的 L1 缓存中,然后在紧密的循环(即没有函数调用)中遍历。
相比于每条记录的处理都需要大量函数调用和条件判断的代码,CPU 执行这样一个循环要快得多。列压缩允许列中的更多行被同时放进容量有限的 L1 缓存。前面描述的按位 “与” 和 “或” 运算符可以被设计为直接在这样的压缩列数据块上操作。这种技术被称为矢量化处理(vectorized processing)。
列式存储中的排序顺序
在列式存储中,存储行的顺序并不关键。按插入顺序存储它们是最简单的,因为插入一个新行只需要追加到每个列文件。
但是,我们也可以选择按某种顺序来排列数据,就像对 SSTables 所做的那样,并将其用作索引机制。
SSTable(Sorted String Table)是一种用于存储键值对的数据结构,它将键值对按照键排序后存储在磁盘上,以便于快速查找和访问。
注意,对每列分别执行排序是没有意义的,因为那样就没法知道不同列中的哪些项属于同一行。我们只能在明确一列中的第 k 项与另一列中的第 k 项属于同一行的情况下,才能重建出完整的行。
相反,数据的排序需要对一整行统一操作,即使它们的存储方式是按列的
。
数据库管理员可以根据他们对常用查询的了解,来选择表格中用来排序的列。
- 例如,如果查询通常以日期范围为目标,例如“上个月”,则可以将date列作为第一个排序键。这样查询优化器就可以只扫描近1个月范围的行了,这比扫描所有行要快得多。
对于第一排序列中具有相同值的行,可以用第二排序列来进一步排序。
- 例如,如果date列是第一个排序关键字,那么 product_sk 可能是第二个排序关键字,以便同一天的同一产品的所有销售数据都被存储在相邻位置。这将有助于需要在特定日期范围内按产品对销售进行分组或过滤的查询。
按顺序排序的另一个好处是它可以帮助压缩列。如果主要排序列没有太多个不同的值,那么在排序之后,将会得到一个相同的值连续重复多次的序列。一个简单的游程编码可以将该列压缩到几 KB —— 即使表中有数十亿行。
第一个排序键的压缩效果最强。第二和第三个排序键会更混乱,因此不会有这么长的连续的重复值。排序优先级更低的列以几乎随机的顺序出现,所以可能不会被压缩。但对前几列做排序在整体上仍然是有好处的。
几个不同的排序顺序
既然不同的查询受益于不同的排序顺序,为什么不以几种不同的方式来存储相同的数据呢?
反正数据都需要做备份,以防单点故障时丢失数据。因此你可以用不同排序方式来存储冗余数据,以便在处理查询时,调用最适合查询模式的版本。
在一个列式存储中有多个排序顺序有点类似于在一个面向行的存储中有多个次级索引。但最大的区别在于面向行的存储将每一行保存在一个地方(在堆文件或聚集索引中),次级索引只包含指向匹配行的指针。在列式存储中,通常在其他地方没有任何指向数据的指针,只有包含值的列。
- 列式存储系列(一)C-Store
- 列式存储系列(二): Vertica
写入列式存储
上面的优化在数据仓库中都是有意义的,因为其负载主要由分析人员运行的大型只读查询组成。列式存储、压缩和排序都有助于更快地读取这些查询。然而,他们的缺点是写入更加困难。
使用 B 树的就地更新方法对于压缩的列是不可能的。如果你想在排序表的中间插入一行,你很可能不得不重写所有的列文件。由于行由列中的位置标识,因此插入必须对所有列进行一致地更新。
很显然,在数据仓库场景下日志型追加写入更符合当前应用场景,因此,我们首先想到LSM 树。
LSM树(Log-Structured Merge Tree)是一种用于实现键值存储的数据结构。它的设计灵感来源于传统的数据库系统中的日志结构(Log-Structured)和合并树(Merge Tree)。
LSM树的基本思路是将数据存储在多层有序的结构中,每一层结构都是一个有序的键值存储结构,比如B树。当写入数据时,新数据先被追加到LSM树的最顶层,这个顶层结构被称为内存表(MemTable)。当内存表中的数据达到一定的大小或数量时,就将它写入到下一层结构中,这个下一层结构称为磁盘表(DiskTable),并将内存表清空以便继续写入新的数据。
当读取数据时,LSM树会先从内存表中查找,如果内存表中没有找到数据,就会从磁盘表中查找。由于每一层结构都是有序的,所以在查找数据时可以利用这个特点进行优化,比如可以使用二分查找等算法。
当磁盘表的数量越来越多时,为了保证读写性能,需要定期将多个磁盘表合并成一个更大的磁盘表,这个过程称为合并(Merge)。合并操作的目的是将多个磁盘表合并成一个更大的磁盘表,同时去重和排序,使得数据查询时的性能更好。
LSM树的优点在于可以支持高吞吐量的写入操作,并且在数据量非常大的情况下仍然能够保证读取性能。缺点在于需要定期进行合并操作,这个过程会影响系统的性能,并且在某些情况下可能会导致数据的不一致。
所有的写操作首先进入一个内存中的存储,在这里它们被添加到一个已排序的结构中,并准备写入硬盘。内存中的存储是面向行还是列的并不重要。当已经积累了足够的写入数据时,它们将与硬盘上的列文件合并,并批量写入新文件。这基本上是 Vertica 所做的。
查询操作需要检查硬盘上的列数据和内存中的最近写入,并将两者的结果合并起来。但是,查询优化器对用户隐藏了这个细节。从分析师的角度来看,通过插入、更新或删除操作进行修改的数据会立即反映在后续的查询中。
聚合:数据立方体和物化视图
数据仓库的另一个值得一提的方面是物化聚合(materialized aggregates)。
- 数据仓库查询通常涉及一个聚合函数,如 SQL 中的 COUNT、SUM、AVG、MIN 或 MAX。
- 如果相同的聚合被许多不同的查询使用,那么每次都通过原始数据来处理可能太浪费了。为什么不将一些查询使用最频繁的计数或总和缓存起来?
创建这种缓存的一种方式是物化视图(Materialized View)。
- 在关系数据模型中,它通常被定义为一个标准(虚拟)视图:
- 一个类似于表的对象,其内容是一些查询的结果。
- 不同的是,物化视图是查询结果的实际副本,会被写入硬盘,而虚拟视图只是编写查询的一个捷径。
- 从虚拟视图读取时,SQL 引擎会将其展开到视图的底层查询中,然后再处理展开的查询。
虚拟视图和物化视图是数据库中视图的两种类型。视图是从一个或多个表中派生出来的虚拟表,它只包含从这些表中选取的数据的逻辑表示,而不是实际的数据。虚拟视图和物化视图的主要区别在于它们的数据存储方式和查询效率。
虚拟视图(也称作“查询视图”)是一个查询语句的结果集,它是一个虚拟表,不实际存储数据,而是在查询时动态生成。虚拟视图可以包括复杂的 SQL
查询,可以从一个或多个表中选择、过滤、连接数据,然后将结果集作为视图返回给用户。虚拟视图的优点是节省存储空间、方便管理和维护,并且在查询时可以实时计算和返回最新的数据。物化视图(也称作“快照视图”)是一个预先计算和存储在磁盘上的视图,它实际上是一个包含数据的表。物化视图是从一个或多个表中选取、过滤、连接数据并将结果存储在表中。当一个查询请求访问物化视图时,它不必重新计算数据,而是直接从物化视图中检索数据。物化视图的优点是可以提高查询性能、降低响应时间和避免频繁查询。
虚拟视图和物化视图的选择取决于具体应用场景。虚拟视图适用于数据量小、查询频繁、查询性能要求不高的情况下;而物化视图适用于数据量大、查询复杂、查询性能要求高的情况下。物化视图的缺点是占用存储空间、数据更新和维护成本高,因此需要谨慎使用。
当底层数据发生变化时,物化视图的内容可能会变得过时,因此需要对物化视图进行更新,以保持其正确性和一致性。这种更新操作通常会增加写入成本,因此在 OLTP 数据库中不经常使用物化视图。
相比之下,在读取繁重的数据仓库中,使用物化视图可以更有意义。在这种情况下,由于查询频繁且复杂,因此使用物化视图可以提高查询性能,降低查询成本。物化视图允许在查询的结果集上进行预计算和预聚合,从而减少实际查询所需的计算和聚合工作量,提高查询性能。此外,由于物化视图是预先计算和预聚合的,因此对于一些查询请求,物化视图可以直接返回结果,从而避免了实时计算和聚合的成本。
需要注意的是,使用物化视图也有一些限制和注意事项。
- 由于物化视图是数据的非规范化副本,因此需要确保其内容与底层数据的一致性和正确性。
- 此外,由于物化视图的更新操作可能会增加写入成本,因此需要权衡利弊,根据具体的场景和需求选择合适的技术方案。
"非规范化副本"是指物化视图中包含的数据不符合数据库中的规范化设计原则,即存在冗余和重复的数据。
- 这种冗余和重复的数据是为了提高查询性能和降低查询成本而特意引入的,因为物化视图会将一些复杂的查询结果预先计算和存储起来,以便在查询时能够快速地获取结果。
- 这就意味着,物化视图中的数据可能与底层数据存在差异,因为物化视图的更新可能滞后于底层数据的更新,或者因为底层数据的更新没有及时地反映到物化视图中。
物化视图的常见特例称为数据立方体或 OLAP 立方。它是按不同维度分组的聚合网格,如下所示:
数据立方的两个维度,通过求和聚合
如上图所示,现在每个事实都只有两个维度表的外键,分别是日期和产品。
- 你现在可以绘制一个二维表格,一个轴线上是日期,另一个轴线上是产品。每个单元格包含具有该日期 - 产品组合的所有事实的属性(例如 net_price)的聚合(例如 SUM)。
- 然后,你可以沿着每行或每列应用相同的汇总,并获得减少了一个维度的汇总(按产品的销售额,无论日期,或者按日期的销售额,无论产品)。
一般来说,事实往往有两个以上的维度。假设我们有五个维度:日期、产品、商店、促销和客户。
要想象一个五维超立方体是什么样子是很困难的,但是原理是一样的:每个单元格都包含特定日期 - 产品 - 商店 - 促销 - 客户组合的销售额。这些值可以在每个维度上求和汇总。
物化数据立方体的优点是可以让某些查询变得非常快,因为它们已经被有效地预先计算了。
- 例如,如果你想知道每个商店的总销售额,则只需查看合适维度的总计,而无需扫描数百万行的原始数据。
数据立方体的缺点是不具有查询原始数据的灵活性。
- 例如,没有办法计算有多少比例的销售来自成本超过 100 美元的项目,因为价格不是其中的一个维度。
- 因此,大多数数据仓库试图保留尽可能多的原始数据,并将聚合数据(如数据立方体)仅用作某些查询的性能提升手段。
小结
总体上来看,存储引擎分类以下两类:
- 针对 事务处理(OLTP) 优化的存储引擎和针对 在线分析(OLAP) 优化的存储引擎。
这两类使用场景的访问模式之间有很大的区别:
- OLTP 系统通常面向最终用户,这意味着系统可能会收到大量的请求。为了处理负载,应用程序在每个查询中通常只访问少量的记录。应用程序使用某种键来请求记录,存储引擎使用索引来查找所请求的键的数据。硬盘查找时间往往是这里的瓶颈。
- 数据仓库和类似的分析系统会少见一些,因为它们主要由业务分析人员使用,而不是最终用户。它们的查询量要比 OLTP 系统少得多,但通常每个查询开销高昂,需要在短时间内扫描数百万条记录。硬盘带宽(而不是查找时间)往往是瓶颈,列式存储是针对这种工作负载的日益流行的解决方案。
在 OLTP 这一边,我们能看到两派主流的存储引擎:
- 日志结构学派:只允许追加到文件和删除过时的文件,但不会更新已经写入的文件。
- Bitcask、SSTables、LSM 树、LevelDB、Cassandra、HBase、Lucene 等都属于这个类别。
- 就地更新学派:将硬盘视为一组可以覆写的固定大小的页面。
- B 树是这种理念的典范,用在所有主要的关系数据库和许多非关系型数据库中。
日志结构的存储引擎是相对较新的技术。他们的主要想法是,通过系统性地将随机访问写入转换为硬盘上的顺序写入,由于硬盘驱动器和固态硬盘的性能特点,可以实现更高的写入吞吐量。