事务处理与分析处理

事务处理与分析处理

OLTP vs OLAP

动机:早期的数据库通常对应于商业交易领域,例如销售、订单、支付员工工资等。后来当数据库扩展到了不涉及金钱的领域,事务一词仍然存在,主要指 组成一个逻辑单元的一组读写操作。即使数据库被用于例如博客的评论、游戏中的动作、通讯录中的联系人等,其基本的访问模式仍然与处理业务交易类似。应用程序通常使用索引中的某些键查找 少量记录,因为这些应用程序是交互式的,所以访问模式被称为 在线事务处理(online transaction processing)。

不过随着商业进步,数据出现了一些不同访问模式,下面有几个是不同的.

某在线游戏中指定游戏角色的等级是多少

购物网站中一个订单记录

博客里的某篇文章获得了多少点赞

一月份店铺的总收入是多少

在最近的促销期间,比平时多卖了多少香蕉

哪个品牌的婴儿食品最常与某品牌的尿布一起购买

最后三项属于分析查询。通常,分析查询需要扫描大量记录,每个记录只读取少数几列,并计算汇总统计信息。

这些业务通常由业务分析师编写,以形成有助于公司管理层更好的决策。为了区分使用数据库与事务处理的模式,称之为 在线分析处理(online analytic processing)

注:OLAP 中 online 的含义尚不清楚。可能是指查询不仅仅针对预定义的报告,而且分析人员以交互方式使用OLAP系统进行探索性查询

对比事务处理与事务分析系统的主要特性

对比事务处理与事务分析系统的主要特性

数据仓库

几乎所有的大型企业都有数据仓库,但是在小型企业中却几乎闻所未闻。大多数小公司只拥有少量的数据,完全可以在传统的SQL数据库中直接进行查询分析。

https://vonng.gitbooks.io/ddia-cn/content/img/fig3-8.png

事实证明,本章前半部分讨论的索引算法适合 OLTP ,但不擅长应对分析查询。在后面的部分,将重点介绍针对分析型而优化的储存引擎。

OLTP数据库和数据仓库之间的差异

为什么会出现这样的差异呢,不要忘记上面的表格~

数据仓库最常见的是关系型,因为SQL通常适合分析查询。有许多图形化数据分析工具,他们可以生成SQL查询、可视化结果并支持分析师探索数据。

一些数据库(例如Microsoft SQL Server和SAP HANA)在同一产品中支持事务处理和数据仓库。然而,它们越来越成为两个独立的存储和查询引擎,这些引擎恰好可以通过一个通用的SQL界面进行访问。(很成功的抽象)

  • 一些商业数据仓库:Teradata,Vertica,SAP HANA和ParAccel等
  • 开源的基于Hadoop的SQL项目:Apache Hive,Spark SQL,Cloudera Impala,Facebook Presto,Apache Tajo和Apache Drill

星型与雪花型分析模式

如第二章所述,根据不同的应用需求,事务处理领域广泛使用了多种不同数据模型。而另一方面,分析型业务的数据模型则要少得多。许多数据仓库都相当公式化的使用了星型模式,也称维度建模

下图所示的模式可用于零售数据仓库。

https://vonng.gitbooks.io/ddia-cn/content/img/fig3-9.png

事实表中的列是属性,其它列可能会引用其他表的外键。由于事实表的每一行都代表一个事件,维度通常代表事件的对象。

“星型模式”这个名字来源于这样一个事实,即当表关系可视化时,事实表在中间,由其它维度表包围;与这些表的连接就像星星的光芒。

该模式的一个变体称为雪花模式,其中的维度被进一步细分为子空间。雪花模式比星型模式更规范化,但是星型模式通常是首选,主要是因为对于分析人员,星型模式使用起来更简单。

在上面的例子中出现的 dim_product 表,每一行都可以再次引用品牌和类别做为外键,不是将其做为字符串直接存储在dim_product 表中。

列存储

数据仓库中的数据会达到PB这个量级,查询是与OLAP不同的。

分析人们是否更倾向于购买新鲜水果或糖果,这取决于一周中的哪一天

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;

我们如何有效地执行这个查询?

一个数据仓库可能会有数百列,不过在数据分析时,往往只需要其中的四到五列即可,如何让这样的查询更加高效呢。

一个很自然的想法就是,何不把每个列单独存在一个文件中呢。

面向列的存储背后的想法很简单:不要将所有来自一行的值存储在一起,而是将来自每一列的所有值存储在一起。如果每个列存储在一个单独的文件中,查询只需要读取和解析查询中使用的那些列,这可以节省大量的工作。

列存储示意图

列压缩

从上面的图不难发现,每一列的属性相似度很高。我们不难想出,这样的形式很有利于压缩。

在数据仓库中有很多中压缩方式,位图编码是特别有效的一种。

位图编码:通常情况下,一列中不同值的数量与行数相比较小(例如,零售商可能有数十亿的销售交易,但只有100,000个不同的产品)。现在我们可以得到一个有 n 个不同值的列,并把它转换成 n 个独立的位图:每个不同值的一个位图,每行一位。如果该行具有该值,则该位为 1 ,否则为 0 。

https://vonng.gitbooks.io/ddia-cn/content/img/fig3-11.png

如果 n 非常小(例如,国家/地区列可能有大约200个不同的值),则这些位图可以每行存储一位。但是,如果n更大,大部分位图中将会有很多的零(我们说它们是稀疏的)。在这种情况下,位图可以另外进行游程编码,如图3-11底部所示。这可以使列的编码非常紧凑。

这些位图索引非常适合数据仓库中常见的各种查询。例如:

WHERE product_sk IN(30,68,69)

例如这样子的查询,可以直接计算三个位图的按位或完成。

WHERE product_sk = 31 AND store_sk = 3

这样的查询可以通过按位与完成,因为列存储中,所有列的顺序是相同的。

此外对于不同种类的数据,也有各种不同的压缩方案,不一一介绍啦。

面向列的存储和列族

Cassandra和HBase有一个列族的概念,他们从Bigtable继承【9】。然而,把它们称为面向列是非常具有误导性的:在每个列族中,它们将一行中的所有列与行键一起存储,并且不使用列压缩。因此,Bigtable模型仍然主要是面向行的。

内存带宽和向量处理

数据仓库超大数据的计算瓶颈:

  1. 数据从磁盘加载到内存
  2. 内存带宽用于CPU缓存(避免分支错误预测)

面向列的存储有利于高效利用CPU周期。例如,,查询引擎可以将大量压缩的列数据放在CPU的L1缓存中,然后在紧密的循环中循环(即没有函数调用)。

诸如前面位图提到的按位AND和OR的运算符,可被设计成这样的列压缩数据块进行操作。这种技术被称为矢量化处理。

列存储中的排序顺序

在列存储中,存储行的顺序并不一定很重要(因为大多数操作是聚合操作)。按插入顺序存储它们是最简单的,因为插入一个新行就意味着附加到每个列文件。但是,我们可以选择强制执行一个命令,就像我们之前对SSTables所做的那样,并将其用作索引机制。

注意,每列独自排序是没有意义的,因为那样我们就不会知道列中的哪些项属于同一行。我们只能重建一行,因为我们知道一列中的第k项与另一列中的第k项属于同一行。

排序后的好处:

  • 排序键的查询速度会加快
  • 可以帮助进一步压缩列

几个不同的排序顺序

为了保证数据安全,我们通常不会只保存一份数据仓库。既然如此,不妨存储不同方式排序的冗余数据,以便在处理查询时,可以选择最适合的特定查询模式的排序版本。

商业数据仓库 Vertica 采用了这样的方法。

列存储的写操作

列存储的初衷是面向大量数据的读操作的,不难想到,列存储的写操作会相对困难。

B-tree 的更新方式对于压缩的列是不可能的,为什么?(考虑插入新的一行后,其中各列的位置变化)

好在LSM-tree 是一个很好的解决方案。所有的写操作首先进入一个内存中的存储,在这里它们被添加到一个已排序的结构中,并准备写入磁盘。内存中的存储是面向行还是列的,这并不重要。当已经积累了足够的写入数据时,它们将与磁盘上的列文件合并,并批量写入新文件。

查询优化器会对用户隐藏上述的技术细节,因此,数据分析师的眼中,更新的数据可以立即反映在稍后的查询中。

聚合:数据立方体和物化视图

并不是每个数据仓库都必定是一个列存储:传统的面向行的数据库和其他一些架构也被使用。然而,对于专门的分析查询,列式存储可以显著加快,所以它正在迅速普及。

数据仓库的另一个值得一提的是物化聚合。如前所述,数据仓库查询通常涉及一个聚合函数,如SQL中的COUNT,SUM,AVG,MIN或MAX。为什么不创建一个缓存保存这种结果呢?

创建这种缓存的一种方式是物化视图。在关系数据模型中,它通常被定义为一个标准(虚拟)视图:一个类似于表的对象,其内容是一些查询的结果。不同的是,物化视图是查询结果的实际副本,被实际的写在硬盘中,而虚拟视图只是编写查询的快捷方式。

当底层数据发生变化时,物化视图需要更新,因为它是数据的非规范化副本。数据库可以自动完成,但是这样的更新使得写入成本更高,因此 OLTP 中一般不常用。

物化视图的常见特例称为数据立方体或OLAP立方。它是按不同维度分组的聚合网格。如图所示。

https://vonng.gitbooks.io/ddia-cn/content/img/fig3-12.png

数据立方的两个维度,通过求和聚合

物化数据立方体的优点是某些查询变得非常快,因为它们已经被有效地预先计算了。缺点是数据立方体不具有查询原始数据的灵活性。例如,没有办法计算哪个销售比例来自成本超过100美元的项目,因为价格不是其中的一个维度。

本章小结

在本章中,我们试图深入了解数据库如何处理存储和检索。将数据存储在数据库中会发生什么,以及稍后再次查询数据时数据库会做什么?

在高层次上,我们看到存储引擎分为两大类:优化 事务处理(OLTP) 或 在线分析(OLAP) 。这些用例的访问模式之间有很大的区别:

  • OLTP系统通常面向用户,这意味着系统可能会收到大量的请求。为了处理负载,应用程序通常只访问每个查询中的少部分记录。应用程序使用某种键来请求记录,存储引擎使用索引来查找所请求的键的数据。磁盘寻道时间往往是这里的瓶颈。
  • 数据仓库和类似的分析系统会低调一些,因为它们主要由业务分析人员使用,而不是由最终用户使用。它们的查询量要比OLTP系统少得多,但通常每个查询开销高昂,需要在短时间内扫描数百万条记录。磁盘带宽(而不是查找时间)往往是瓶颈,列式存储是这种工作负载越来越流行的解决方案。

在OLTP方面,我们能看到两派主流的存储引擎:

日志结构学派

只允许附加到文件和删除过时的文件,但不会更新已经写入的文件。 Bitcask,SSTables,LSM树,LevelDB,Cassandra,HBase,Lucene等都属于这个类别。

就地更新学派

将磁盘视为一组可以覆写的固定大小的页面。 B树是这种哲学的典范,用在所有主要的关系数据库中和许多非关系型数据库。

日志结构的存储引擎是相对较新的发展。他们的主要想法是,他们系统地将随机访问写入顺序写入磁盘,由于硬盘驱动器和固态硬盘的性能特点,可以实现更高的写入吞吐量。在完成OLTP方面,我们通过一些更复杂的索引结构和为保留所有数据而优化的数据库做了一个简短的介绍。

然后,我们从存储引擎的内部绕开,看看典型数据仓库的高级架构。这一背景说明了为什么分析工作负载与OLTP差别很大:当您的查询需要在大量行中顺序扫描时,索引的相关性就会降低很多。相反,非常紧凑地编码数据变得非常重要,以最大限度地减少查询需要从磁盘读取的数据量。我们讨论了列式存储如何帮助实现这一目标。

作为一名应用程序开发人员,如果您掌握了有关存储引擎内部的知识,那么您就能更好地了解哪种工具最适合您的特定应用程序。如果您需要调整数据库的调整参数,这种理解可以让您设想一个更高或更低的值可能会产生什么效果。

尽管本章不能让你成为一个特定存储引擎的调参专家,但它至少有大概率使你有了足够的概念与词汇储备去读懂数据库的文档,从而选择合适的数据库。