数据库应用之数据分析
在早期数据库发明的时候主要是用来为实现商业功能的,比如说保存订单的信息,支付员工的工资等等。这类需求更多地是面向功能的,它的要求是相关的请求能够快速及时正确的执行,我们称这个流程为联机事务处理(OLTP, Online Transaction Processing)。
而随着数据库发展至今,一个更加常见的应用场景就是数据分析。比如说如何从淘宝订单中分析出商家每个月的销售情况,如何分析出哪些商品是爆款,如何得到某个新的功能给公司带来的点击量的增加等等。我们通常称这个流程为联机分析处理(OLAP, Online analytic processing)。
通常来说OLAP都是由商务部门来使用,最终呈现的形式可能是一个报告,因而它和我们传统的联机事务处理是不同的,它们主要的差别见下表:
场景 | OLTP | OLAP |
读 | 每次查询的记录数据比较小,通常是查询某个key的情况 | 大量记录的聚合数据 |
写 | 随机访问,希望写的延时很小 | 批量导入或者event流 |
主要使用者 | 终端的用户 | 内部分析 |
数据显示方法 | 当前最新的数据 | 某段时间的历史数据 |
数据量级 | GB 到TB | TB 到PB |
Data Warehouse
在早期的时候,大家都会使用同样的数据库来进行OLAP的分析,后来越来越多的公司会使用专用的数据库来做这件事情,我们称之为Data warehouse。
当我们要使用一个独立的Data warehouse的时候,首先需要解决的问题就是如何获得相关的数据,通常来说,会从OLTP数据库来获取数据(使用一个周期性的数据dump或者连续的更新流等等),然后通过一定的流程把这些数据转换成特定分析的schema,再上传到相应的data warehouse。这个流程一般称之为ETL(Extract-Transform-Load)。大体的流程见下图:
使用独立的data warehouse来进行分析的一个好处就是我们可以为它专门做一些查询分析的优化而不会影响原有的OLTP数据库。下面我们就来看一下一般会有哪些优化。
星型模型
对data warehouse来说,数据模型的变化其实蛮小的,所以一般会使用比较固定的格式来存储,我们称之为星型模型。下图就是一个零售店的例子,这里我们可以看到有一个大的“中心”表格: fact_sales,我们称这个表为事实表,它用来保存某个特定时间发生的事情。这里就是每天某个产品售卖的情况。因为这个表要记录几乎所有的独立事件以便于分析,这对一些大型的公司,比如淘宝,京东来说,这个数据量就会非常大,可能会到几十个PB的量级甚至更大。
我们可以看到这个事实表其实会关联很多别的相关的表,比如时间,通常来说直接使用时间的值就可以了,但是有时我们需要知道是否有特定的事件在这个时间段发生,比如说是不是圣诞节,是不是情人节之类的,这些信息就需要从相关表格(dim_data)中获取。
这种模型之所以称为星型模型,也正是因为以事实表为中心,然后和别的各种表格关联,就像一个星的射线一样。
顺带说一下,还有一种模型称之为雪花模型,在这种模型中并不是每个表格都和中间事实表相关联,而是通过别的表间接和事实表相连接,就像雪花一样。通常来说,因为星型模型更方便分析,所以在这里会更加推荐。
面向列的存储
通常来说事实表为了保存足够的数据用来分析,它的列会有几百列以处理不同的分析需求,但是我们每个特定的分析使用的列数量并不是很多,可能就只有4,5个。
比如说我们想分析每天客户购买新鲜水果和糖果的情况,我们可以使用下面的查询来实现:
那么这样的查询如何来优化呢?假如是在OLTP的数据库中,每行数据存储在一起,我们首先想到的就是把data_key和product_sk加上索引,不过这样做的问题就在于我们每次其实还是加载的是一行数据,也就意味着我们可能会浪费时间在加载其他几百列完全用不到的数据上。这显然不是一个很好的方法。这里就有了一个新的想法,就是我们在保存数据的时候不再是按照行来存储,取而代之的是按照列来存储,这样做的好处就是我们再遇到上面的查询就可以只访问需要的几个列就可以了。具体的示意图如下所示:
当我们按照列来存储之后,那就可以基于这种思想来做很多优化了。比如说我们列里面不同的值比较少,那就可以使用下面这种方法来压缩数据,这种方法的中心思想就是用一个bitmap来现实这些不同的值都出现在哪些行(就是列存储中的第几个)。比如说这里的29,我们可以看到它是第十行存在,那就是9个0,然后第十行是1,就是1个1,然后后面都是0.
这种压缩的方法在不同值的个数和行数之间相差很大的情况下效果特别好。而且这种存储对一些查询也很友好,比如说WHERE product_sk IN (30, 68, 69):我们只要把product_sk =30和product_sk=68以及69的三个bitmap取出来,再或一下,就得到想要的结果了。是不是特别方便?
另外一点就是有时我们需要做范围的查询,而各个column其实保存的顺序并不重要(每个column自身内容的顺序不能变,因为是对应到相应的行),所以我们可以在保存的时候就按照column的值进行保存,这样范围查询就会得到一定的帮助。
但是有时我们不同的查询会想要不同的排序方式。在这个方面有没有什么可以优化的空间呢?答案是有的,一个有趣的想法就是我们通常会为数据库保存不同的replia备份,而两个备份之间其实顺序并不是那么重要(只要最终内容是相同的即可),所以我们可以在不同的备份之间按照不同的顺序来组织,这样就能满足不同的排序需求了,只是说备份恢复以及不同查询的指向会需要一些额外的处理。
物化视图
Data warehouse的另外一个特点就是会有很多聚合的操作,比如COUNT, SUM, MIN 或者MAX等等。而这些聚合操作需要处理很多数据,因此相对来说速度就会比较慢。考虑到data warehouse的特性,就是读远大于写,我们其实可以把这些聚合的结果cache起来,这样就不需要每次都来计算这些聚合的数据了。
通常cache这些数据的手法就是物化视图(Materialized view),它和平常数据库中的视图是类似的,比较大的差别就在于它会把数据真正写到磁盘中。这也就意味着我们需要更新它的数据,所以这也是这种技术在我们OLTP的数据库中通常不使用的原因。
物化视图的一个常见的case就是数据立方体(data cube),如下图所示,其实这个就和我们的excel表来计算聚合信息是类似的,我想大家一看应该就会明白。
总结
这篇文章我们主要讨论了OLAP和OLTP的处理差别,尤其是针对其的Data warehouse的实现和相关的优化,希望大家在阅读完成之后对OLAP的相关处理能有一个大概的概念。
Recent Comments