① 大数据之路
人类从“IT时代”进入“DT时代”。本书介绍了阿里巴巴的大数据系统架构,为了满足不断变化的业务需求,同时实现系统的 高扩展性 、 灵活性 以及 数据展现的高性能 。
数据体系主要包括: 数据采集 、 数据计算 、 数据服务 和 数据应用 四大层次。
事实表包括引用的 维度 和描述具体业务的 度量 。
事实表中一条记录描述的业务的细节程度称为 粒度 。粒度可以使用两种方式来表示:(1)维度属性组合(2)所表示的具体业务含义。
事实包括可加性、半可加性和不可加性三种类型:
半可加性:只可以针对特定维度做聚合,例如库存(不能按照日期,可按照仓库聚合)。
可加性:可以按照任意维度聚合。
不可加性:完全不具备可加性。(例如:比率,事实表可以拆分存储分子分母)
维度属性也可以存到事实表中,称为 退化维度 。
事实表有三种类型:事务事实表、周期快照事实表、累计快照事实表。
事务事实表描述的是业务过程上的原子事务,也称为 原子事实表 。
周期快照事实表是按照周期性规律的时间间隔记录事实。
累计快照事实表:累计快照事实表用来表示过程开始和结束过程之间的关键步骤事件,覆盖整个生命周期,通常用多个日期字段记录关键时间点,记录会随着时间变化而修改。
事实表设计原则:
原则1: 尽可能包含所有与业务过程相关的事实。
即时存在冗余,也尽可能存储。
原则2:只选择与业务过程相关的事实。
原则3:分解不可加事实为可加的组件。
例如:不存成单率,转而存储成单数和提单数。
原则4:选择维度和事实前,必须先声明粒度。
建议粒度设置的越细越好,这样可以最大限度的提高灵活性。可以通过业务描述或者维度属性组合的方式来定义粒度。
原则5:在同一个事实表中,不应该有不同粒度的事实。
例如:一个事实表中不应该包含某些精确到订单粒度的度量,同时又包含只精确到城市的度量。
原则6:事实的单位一致。
原则7:尽量处理掉事实表中的null值。
sql中大于,小于的条件不适用与null值,所以尽量用数值替代null,例如0.
原则8:使用退化维度增加事实表的易用性。
在Kimball的维度设计模型中,分拆出单独的维度表,为了节省存储。但是为了减少使用时的关联次数,可以多使用退化维度提供事实表易用性。
事实表设计方法:
1.选择业务过程及确定事实表类型。2. 声明粒度。3.确定维度。4.确定事实。5.冗余维度(设计退化维度)。
事务事实表,即针对业务过程构建的一类事实表,用来跟踪定义业务过程的个体行为,提供丰富的分析能力,作为数据仓库原子的明细数据。
单事务事实表,即针对每一个业务过程设计一个事实表,这样可以方便地对每一个业务过程进行分析研究。
表示同一个事实表包含不同的业务过程。多事务事实表有两种实现方法:(1)使用两个不同的事实字段来保存各自业务过程。(2)使用同一个字段保存,但是增加一个业务过程标签。
下面举例说明,淘宝交易事务事实表同时包含下单、支付和成功完结三个过程,三个过程粒度一致,可以放在一个事实表。下面确定维度和事实,该表中的下单度量、支付度量和成功完结度量信息分别存在不同字段,如果不是当前业务处理,则用0来处理。
当不同业务过程的度量比较相似、差异不大时使用第二种事实表(使用一个字段保存),当不同业务过程的度量差异大时,使用第一种(多字段保存)。
对于单事务事实表和多事务事实表的选择上,可以从以下一些方面来区分:
业务过程、粒度和维度(不同业务过程粒度相同,并且维度相似时,可以选用单事务事实表)、事实、下游业务使用、计算存储成本。电商环境下,有父子订单的概念,店铺多商品各生成一个订单,在一个店铺合成一个父订单。
1.事实完整性:事实表包含与其描述的过程有关的所有事实。
2.事实一致性:明确存储每一个事实以确保度量一致性。例如,有下单商品数和商品价格2个事实,同时保存下单金额(价格*商品数)。这样下游使用时,直接取下单金额,而不是再次计算,以保证指标的一致性。
3.事实可加性:为确保下游使用时,指标的可聚合性,尽量保存原始数,而不是计算后的比率指标。
对于事务度量,事务性事实表可以很好地表征。但是对于一些 状态度量 ,例如买卖家累计交易金额、商品库存、买卖家星级、温度(事务事实表无法聚合得到)等,事务事实表的效率较低或者无法处理。为了解决状态度量问题,引入周期性快照事实表(也称为 快照事实表 )。
1.用快照采样状态:快照事实表以预定的间隔采样状态度量。
2.快照粒度:快照事实表通常总是被多维声明,即快照需要采样的周期以及什么将被采样。
3.密度和稠密性:稠密性是快照事实表的重要特征。事务事实表一般都是稀疏的,只要发生业务才会有相应记录。
4.半可加性:快照事实表的状态度量都是半可加的,例如商品库存,只针对商品维度可加,对日期维度不可加。
设计快照事实表,首先确定快照粒度,然后确定采样的状态度量。下面介绍几个快照事实表实例。
单维度每天快照事实表、混合维度每天快照事实表,这两种快照表都可以从事务事实表汇总得到。另外的一种产出模式是直接使用操作型系统作为数据源来加工,例如淘宝卖家的星级评分是在操作型系统中计算得出的,仓库直接拿来这部分数据加入事实表。全量快照事实表,是特殊类型的周期快照表,例如设计无事实的事实表来记录评论的状态度量。
对于研究事件之间的时间间隔需求时,累计快照事实表能较好符合需求。
特点:
1.数据不断更新:例如,在下单、支付和确认收货三个业务过程中,事务事实表会生成3条记录,而累计快照表会不断更新一条记录(不生成新记录)。
2.多业务过程日期:
累计快照表适用于具有较明确起止时间的短生命周期的实体,对于每个实体都经历从诞生到消亡等步骤。
3.存储历史全量数据。
1.事件类的,例如浏览日志。
2.条件范围资格类的,例如客户和销售人员的分配情况。
主要是提前聚合,为了增加数据访问的效率(不用再聚合了),减少数据不一致的情况。这类聚集汇总数据,被称为“公共汇总层”。
聚集的基本步骤:1.确定聚集维度。2.确定一致性上钻。3.确定聚集事实。
元数据主要记录数据仓库中模型的定义、各层级间映射关系、监控数据仓库的数据状态及ETL任务的运行状态。元数据分为 技术元数据 和 业务元数据 。
阿里巴巴技术元数据包括:
数据表、列等信息;ETL作业的信息;数据同步、任务调度、计算任务等信息。数据质量和运维相关元数据。
阿里巴巴业务元数据包括:
维度属性、业务过程、指标等。数据应用元数据,例如数据报表、数据产品等。
元数据价值:
元数据在数据管理方面为集团数据在计算、存储、成本、质量、安全、模型等治理领域上提供数据支持。
阿里MaxCompute提供了archive压缩方法,采用了具有更高压缩比压缩算法,将数据以RAID file的形式存储。这样可以节省空间,但是恢复起来也更复杂,所以适用于冷备份的数据。
MaxCompute基于列存储,通过修改表的数据重分布,避免列热点,将会节省一定存储空间。
存储治理项以元数据为基础,列出例如“62天内未访问的分区”、“数据无更新的任务列表”等等管理项推动ETL优化。形成现状分析、问题诊断、管理优化、效果反馈的存储治理项优化的闭环。
生命周期管理的目的是用最少的存储成本来满足最大业务需求,实现数据价值最大化。
1.周期性删除策略:
2.彻底删除策略:主要针对无用表,ETL中间过程表。
3.永久保存策略:
4.极限存储策略:
5.冷数据管理策略:针对重要且访问频率低的数据。
6.增量表merge全量表策略:
将一个数据表的成本分为存储成本和计算成本,除此之外,上游表对该表的扫描成本也应该计入。相应的计费分别核算为:计算付费、存储付费和扫描付费。数据资产的成本管理分为数据成本计量和数据使用计费。
② 什么是数据库维度 怎么理解怎么用做什么用的 能否通俗易懂的说明。谢谢。
举个简单例子:
就拿excel表格来说,作为单一的工作表,就包含二维(行和列),而一个excel文件,通常包含多个工作表,打开excel文件时,在下方显示的“sheet1、sheet2”这些工作表页列,就是第三维。
excel是最简单的数据库应用,一个xlsx文件只有三维,但你可以用若干个xlsx文件来组成一个项目,这些文件序列,你可以视为第四维。
然后,你还可以把一组组xlsx文件放在一个个目录中,那么这些目录序列,你可以视为第五维。
再往上,你还可以设置更上一级目录,那就是第六维……
反正在excel中,任何一个单元格,都可以调用存储在本地电脑(甚至是网络电脑)任何地方的、任何一个excel文件中的、任何一个工作表的、任何一个单元格内容,所以说,虽然是一大堆的文件,你也可以当做是一个数据库来处理,只是不那么方便。
……
在数据库中,单一的数据库就能包含很多很多维,你也可以把这些维,当做树状目录的结构来理解,也可以当做一堆堆的xlsx文件集合来理解。
磁盘的存储结构(不管是fat还是ntfs,还是linux或os或别的什么磁盘格式),都是一种大型的、多维的数据库,分区是一个维度,目录是一个维度,每一档下级目录又是一个维度。文件是一个维度,文件中的章节行段也是维度……
数学中的维度概念,和通常意义上的空间维度,是两回事。
空间维度可以用数学来解释,但数学维度,三维以上你就无法用空间来显示。
但在数据库中,三维只是基本操作。
……
用excel来举例,已经是我能找到的最容易理解的方案。
我真正理解数据库维度时,是从数组开始的,当时使用一个很简陋的编程软件,他不提供数据库建立和访问,数组的维度也有限,还需要自己建立多维存储文件,并且只支持文本格式。
文本格式中,使用【】标记数组维度,【】中间的标识符可以自定义,通过各种不同的标识符来延伸维度……做着做着,我忽然间就领悟到什么叫数据库、什么叫维度,如果不考虑执行效率的话,用一个文本文件,就能模拟出一个硬盘来……
③ 影响数据检索效率的几个因素
影响数据检索效率的几个因素
数据检索有两种主要形态。第一种是纯数据库型的。典型的结构是一个关系型数据,比如 mysql。用户通过 SQL 表达出所需要的数据,mysql 把 SQL 翻译成物理的数据检索动作返回结果。第二种形态是现在越来越流行的大数据玩家的玩法。典型的结构是有一个分区的数据存储,最初这种存储就是原始的 HDFS,后来开逐步有人在 HDFS 上加上索引的支持,或者干脆用 Elasticsearc 这样的数据存储。然后在存储之上有一个分布式的实时计算层,比如 Hive 或者 Spark SQL。用户用 Hive SQL 提交给计算层,计算层从存储里拉取出数据,进行计算之后返回给用户。这种大数据的玩法起初是因为 SQL 有很多 ad-hoc 查询是满足不了的,干脆让用户自己写 map/rece 想怎么算都可以了。但是后来玩大了之后,越来越多的人觉得这些 Hive 之类的方案查询效率怎么那么低下啊。于是一个又一个项目开始去优化这些大数据计算框架的查询性能。这些优化手段和经典的数据库优化到今天的手段是没有什么两样的,很多公司打着搞计算引擎的旗号干着重新发明数据库的活。所以,回归本质,影响数据检索效率的就那么几个因素。我们不妨来看一看。
数据检索干的是什么事情
定位 => 加载 => 变换
找到所需要的数据,把数据从远程或者磁盘加载到内存中。按照规则进行变换,比如按某个字段group by,取另外一个字段的sum之类的计算。
影响效率的四个因素
读取更少的数据
数据本地化,充分遵循底层硬件的限制设计架构
更多的机器
更高效率的计算和计算的物理实现
原则上的四点描述是非常抽象的。我们具体来看这些点映射到实际的数据库中都是一些什么样的优化措施。
读取更少的数据
数据越少,检索需要的时间当然越少了。在考虑所有技术手段之前,最有效果的恐怕是从业务的角度审视一下我们是否需要从那么多的数据中检索出结果来。有没有可能用更少的数据达到同样的效果。减少的数据量的两个手段,聚合和抽样。如果在入库之前把数据就做了聚合或者抽样,是不是可以极大地减少查询所需要的时间,同时效果上并无多少差异呢?极端情况下,如果需要的是一天的总访问量,比如有1个亿。查询的时候去数1亿行肯定快不了。但是如果统计好了一天的总访问量,查询的时候只需要取得一条记录就可以知道今天有1个亿的人访问了。
索引是一种非常常见的减少数据读取量的策略了。一般的按行存储的关系型数据库都会有一个主键。用这个主键可以非常快速的查找到对应的行。KV存储也是这样,按照Key可以快速地找到对应的Value。可以理解为一个Hashmap。但是一旦查询的时候不是用主键,而是另外一个字段。那么最糟糕的情况就是进行一次全表的扫描了,也就是把所有的数据都读取出来,然后看要的数据到底在哪里,这就不可能快了。减少数据读取量的最佳方案就是,建立一个类似字典一样的查找表,当我们找 username=wentao 的时候,可以列举出所有有 wentao 作为用户名的行的主键。然后拿这些主键去行存储(就是那个hashmap)里捞数据,就一捞一个准了。
谈到索引就不得不谈一下一个查询使用了两个字段,如何使用两个索引的问题。mysql的行为可以代表大部分主流数据库的处理方式:
基本上来说,经验表明有多个单字段的索引,最后数据库会选一最优的来使用。其余字段的过滤仍然是通过数据读取到内存之后,用predicate去判断的。也就是无法减少数据的读取量。
在这个方面基于inverted index的数据就非常有特点。一个是Elasticsearch为代表的lucene系的数据库。另外一个是新锐的druid数据库。
效果就是,这些数据库可以把单字段的filter结果缓存起来。多个字段的查询可以把之前缓存的结果直接拿过来做 AND 或者 OR 操作。
索引存在的必要是因为主存储没有提供直接的快速定位的能力。如果访问的就是数据库的主键,那么需要读取的数据也就非常少了。另外一个变种就是支持遍历的主键,比如hbase的rowkey。如果查询的是一个基于rowkey的范围,那么像hbase这样的数据库就可以支持只读取到这个范围内的数据,而不用读取不再这个范围内的额外数据,从而提高速度。这种加速的方式就是利用了主存储自身的物理分布的特性。另外一个更常见的场景就是 partition。比如 mysql 或者 postgresql 都支持分区表的概念。当我们建立了分区表之后,查找的条件如果可以过滤出分区,那么可以大幅减少需要读取的数据量。比 partition 更细粒度一些的是 clustered index。它其实不是一个索引(二级索引),它是改变了数据在主存储内的排列方式,让相同clustered key的数据彼此紧挨着放在一起,从而在查询的时候避免扫描到无关的数据。比 partition 更粗一些的是分库分表分文件。比如我们可以一天建立一张表,查询的时候先定位到表,再执行 SQL。比如 graphite 给每个 metric 创建一个文件存放采集来的 data point,查询的时候给定metric 就可以定位到一个文件,然后只读取这个文件的数据。
另外还有一点就是按行存储和按列存储的区别。按列存储的时候,每个列是一个独立的文件。查询用到了哪几个列就打开哪几个列的文件,没有用到的列的数据碰都不会碰到。反观按行存储,一张中的所有字段是彼此紧挨在磁盘上的。一个表如果有100个字段,哪怕只选取其中的一个字段,在扫描磁盘的时候其余99个字段的数据仍然会被扫描到的。
考虑一个具体的案例,时间序列数据。如何使用读取更少的数据的策略来提高检索的效率呢?首先,我们可以保证入库的时间粒度,维度粒度是正好是查询所需要的。如果查询需要的是5分钟数据,但是入库的是1分钟的,那么就可以先聚合成5分钟的再存入数据库。对于主存储的物理布局选择,如果查询总是针对一个时间范围的。那么把 timestamp 做为 hbase 的 rowkey,或者 mysql 的 clustered index 是合适。这样我们按时间过滤的时候,选择到的是一堆连续的数据,不用读取之后再过滤掉不符合条件的数据。但是如果在一个时间范围内有很多中数据,比如1万个IP,那么即便是查1个IP的数据也需要把1万个IP的数据都读取出来。所以可以把 IP 维度也编码到 rowkey 或者 clustered index 中。但是假如另外还有一个维度是 OS,那么查询的时候 IP 维度的 rowkey 是没有帮助的,仍然是要把所有的数据都查出来。这就是仅依靠主存储是无法满足各种查询条件下都能够读取更少的数据的原因。所以,二级索引是必要的。我们可以把时间序列中的所有维度都拿出来建立索引,然后查询的时候如果指定了维度,就可以用二级索引把真正需要读取的数据过滤出来。但是实践中,很多数据库并不因为使用了索引使得查询变快了,有的时候反而变得更慢了。对于 mysql 来说,存储时间序列的最佳方式是按时间做 partition,不对维度建立任何索引。查询的时候只过滤出对应的 partition,然后进行全 partition 扫描,这样会快过于使用二级索引定位到行之后再去读取主存储的查询方式。究其原因,就是数据本地化的问题了。
[page]
数据本地化
数据本地化的实质是软件工程师们要充分尊重和理解底层硬件的限制,并且用各种手段规避问题最大化利用手里的硬件资源。本地化有很多种形态
最常见的最好理解的本地化问题是网络问题。我们都知道网络带宽不是无限的,比本地磁盘慢多了。如果可能尽量不要通过网络去访问数据。即便要访问,也应该一次抓取多一些数据,而不是一次搞一点,然后搞很多次。因为网络连接和来回的开销是非常高的。这就是 data locality 的问题。我们要把计算尽可能的靠近数据,减少网络上传输的数据量。
这种带宽引起的本地化问题,还有很多。网络比硬盘慢,硬盘比内存慢,内存比L2缓存慢。做到极致的数据库可以让计算完全发生在 L2 缓存内,尽可能地避免频繁地在内存和L2之间倒腾数据。
另外一种形态的问题化问题是磁盘的顺序读和随机读的问题。当数据彼此靠近地物理存放在磁盘上的时候,顺序读取一批是非常快的。如果需要随机读取多个不连续的硬盘位置,磁头就要来回移动从而使得读取速度快速下降。即便是 SSD 硬盘,顺序读也是要比随机读快的。
基于尽可能让数据读取本地化的原则,检索应该尽可能地使用顺序读而不是随机读。如果可以的话,把主存储的row key或者clustered index设计为和查询提交一样的。时间序列如果都是按时间查,那么按时间做的row key可以非常高效地以顺序读的方式把数据拉取出来。类似地,按列存储的数据如果要把一个列的数据都取出来加和的话,可以非常快地用顺序读的方式加载出来。
二级索引的访问方式典型的随机读。当查询条件经过了二级索引查找之后得到一堆的主存储的 key,那么就需要对每个 key 进行一次随机读。即便彼此仅靠的key可以用顺序读做一些优化,总体上来说仍然是随机读的模式。这也就是为什么时间序列数据在 mysql 里建立了索引反而比没有建索引还要慢的原因。
为了尽可能的利用顺序读,人们就开始想各种办法了。前面提到了 mysql 里的一行数据的多个列是彼此紧靠地物理存放的。那么如果我们把所需要的数据建成多个列,那么一次查询就可以批量获得更多的数据,减少随机读取的次数。也就是把之前的一些行变为列的方式来存放,减少行的数量。这种做法的经典案例就是时间序列数据,比如可以一分钟存一行数据,每一秒的值变成一个列。那么行的数量可以变成之前的1/60。
但是这种行变列的做法在按列存储的数据库里就不能直接照搬了,有些列式数据库有column family的概念,不同的设置在物理上存放可能是在一起的也可能是分开的。对于 Elasticsearch 来说,要想减少行的数量,让一行多pack一些数据进去,一种做法就是利用 nested document。内部 Elasticsearch 可以保证一个 document 下的所有的 nested document是物理上靠在一起放在同一个 lucene 的 segment 内。
网络的data locality就比较为人熟知了。map rece的大数据计算模式就是利用map在数据节点的本地把数据先做一次计算,往往计算的结果可以比原数据小很多。然后再通过网络传输汇总后做 rece 计算。这样就节省了大量网络传输数据的时间浪费和资源消耗。现在 Elasticsearch 就支持在每个 data node 上部署 spark。由 spark 在每个 data node 上做计算。而不用把数据都查询出来,用网络传输到 spark 集群里再去计算。这种数据库和计算集群的混合部署是高性能的关键。类似的还有 storm 和 kafka 之间的关系。
网络的data locality还有一个老大难问题就是分布式大数据下的多表join问题。如果只是查询一个分布式表,那么把计算用 map rece 表达就没有多大问题了。但是如果需要同时查询两个表,就意味着两个表可能不是在物理上同样均匀分布的。一种最简单的策略就是找出两张表中最小的那张,然后把表的内容广播到每个节点上,再做join。复杂一些的是对两个单表做 map rece,然后按照相同的 key 把部分计算的结果汇集在一起。第三种策略是保证数据分布的方式,让两张表查询的时候需要用到的数据总在一起。没有完美的方案,也不大可能有完美的方案。除非有一天网络带宽可以大到忽略不计的地步。
更多的机器
这个就没有什么好说的了。多一倍的机器就多一倍的 CPU,可以同时计算更多的数据。多一倍的机器就多一倍的磁头,可以同时扫描更多的字节数。很多大数据框架的故事就是讲如何如何通过 scale out解决无限大的问题。但是值得注意的是,集群可以无限大,数据可以无限多,但是口袋里的银子不会无限多的。堆机器解决问题比升级大型机是要便宜,但是机器堆多了也是非常昂贵的。特别是 Hive 这些从一开始就是分布式多机的检索方案,刚开始的时候效率并不高。堆机器是一个乘数,当数据库本来单机性能不高的时候,乘数大并不能起到决定性的作用。
更高效的计算和计算实现
检索的过程不仅仅是磁盘扫描,它还包括一个可简单可复杂的变换过程。使用 hyperloglog,count min-sketch等有损算法可以极大地提高统计计算的性能。数据库的join也是一个经常有算法创新的地方。
计算实现就是算法是用C++实现的还是用java,还是python实现的。用java是用大Integer实现的,还是小int实现的。不同的语言的实现方式会有一些固定的开销。不是说快就一定要C++,但是 python 写 for 循环是显然没有指望的。任何数据检索的环节只要包含 python/ruby 这些语言的逐条 for 循环就一定快不起来了。
结论
希望这四点可以被记住,成为一种指导性的优化数据检索效率的思维框架。无论你是设计一个mysql表结构,还是优化一个spark sql的应用。从这四个角度想想,都有哪些环节是在拖后腿的,手上的工具有什么样的参数可以调整,让随机读变成顺序读,表结构怎么样设计可以最小化数据读取的量。要做到这一点,你必须非常非常了解工具的底层实现。而不是盲目的相信,xx数据库是最好的数据库,所以它一定很快之类的。如果你不了解你手上的数据库或者计算引擎,当它快的时候你不知道为何快,当它慢的时候你就更加无从优化了。
④ 【总结】维度数据建模过程及举例
本文介绍数据仓库中维度数据建模的过程描述,并举一个示例以加深对相关概念的理解。
维度模型是数据仓库领域大师Ralph Kimall所倡导,他的《数据仓库工具箱》,是数据仓库工程领域最流行的数仓建模经典。维度建模以分析决策的需求出发构建模型,构建的数据模型为分析需求服务,因此它重点解决用户如何更快速完成分析需求,同时还有较好的大规模复杂查询的响应性能。
1、通过对业务需求以及可用数据源的综合考虑,确定对哪种业务过程开展建模工作
2、建立的第一个维度模型应该是一个最有影响的模型——它应该对最紧迫的业务问题作出回答,并且对数据的抽取来说是最容易的。
注:粒度是指数据仓库的数据单位中保存数据的细化或综合程度的级别,细化程度越高,粒度就越小
1、应该先优先考虑为业务处理获取最有原子性的信息而开发维度模型。原子型数据是所收集的最详细的信息,这样的数据不能再做更进一步的细分。
2、数据仓库几乎总是要求在每个维度可能得到的最低粒度上对数据进行表示的原因,并不是因为查询想看到每个低层次的行,而是因为查询希望以很精确的方式对细节知识进行抽取。
一个经过仔细考虑的粒度定义确定了事实表的基本维度特性。同时,经常也可能向事实表的基本粒度加入更多的维度,而这些附加的维度会在基本维度的每个组合值方面自然地取得唯一的值。如果附加的维度因为导致生成另外的事实行而违背了这个基本的粒度定义,那么必须对粒度定义进行修改以适应这个维度的情景。
确定将哪些事实放到事实表中。粒度声明有助于稳定相关的考虑。事实必须与粒度吻合。在考虑可能存在的事实时,可能会发现仍然需要调整早期的粒度声明和维度选择
维度建模中有一些比较重要的概念,理解了这些概念,基本也就理解了什么是维度建模。
额,看了这一句,其实是不太容易理解到底什么是事实表的。
比如一次购买行为我们就可以理解为是一个事实,下面我们上示例。
图中的订单表就是一个事实表,你可以理解他就是在现实中发生的一次操作型事件,我们每完成一个订单,就会在订单中增加一条记录。
我们可以回过头再看一下事实表的特征,在维度表里没有存放实际的内容,他是一堆主键的集合,这些ID分别能对应到维度表中的一条记录。
我们的图中的用户表、商家表、时间表这些都属于维度表,这些表都有一个唯一的主键,然后在表中存放了详细的数据信息。
下面我们将以电商为例,详细讲一下维度建模的建模方式,并举例如果使用这个模型(这点还是很重要的)。
假设我们在一家电商网站工作,比如某宝、某东。我们需要对这里业务进行建模。下面我们分析几点业务场景:
好,基于这几点,我们来设计我们的模型。
下面就是我们设计出来的数据模型,和之前的基本一样,只不过是换成了英文,主要是为了后面写sql的时候来用。
我就不再解释每个表的作用了,现在只说一下为什么要这样设计。
首先,我们想一下,如果我们不这样设计的话,我们一般会怎么做?
如果是我,我会设计下面这张表。你信不信,我能列出来50个字段!其实我个人认为怎么设计这种表都有其合理性,我们不论对错,单说一下两者的优缺点。
先说我们的维度模型:
再说我们这张大款表的优缺点:
数据模型的建立必须要为更好的应用来服务,下面我先举一个例子,来切实地感受一下来怎么用我们的模型。
需求 :求出2016年在帝都的男性用户购买的LV品牌商品的总价格。
实现 :
维度建模是一种十分优秀的建模方式,他有很多的优点,但是我们在实际工作中也很难完全按照它的方式来实现,都会有所取舍,比如说为了业务我们还是会需要一些宽表,有时候还会有很多的数据冗余。
⑤ 写sql语句 一般什么时候出现笛卡尔积啊 如何避免
楼主这个问题,表达的不是很准确。事实上你所说的什么时候出现笛卡尔积,应该是指一对多关系的时候,如果避免重复,而不是如何避免笛卡尔积。笛卡尔积在SQL中是有特殊的关联来求笛卡尔积的,求笛卡尔积的指令是cross join。那么回到如何避免重复的问题上,一般对于SQL开发来说,这是让很多人头疼的问题。一般呢,我个人把重复定义为如下三种情况:
第一种,原数据重复,指的是对应关系表中的数据本身就存在重复。但这种情况并不多,开发的时候会设定主键,一般情况较少。这种情况通常把需要使用的粒度数据distinct后,再关联就可以了。
第二种,就是维度重复。比如有区域表,分别是省市县三列,而你要统计的是到省的数据,这样你直接写join的时候会直接关联出很多条,这样通常使用子查询去除维度重复后,再关联即可
第三种,就是在一对多关系关联出来后的数据维度重复。有些东西是存放很多关系表的,在关系表关联后出现重复数据是个很正常的事情,但是可能由于需求比较特别,这样我们通常对这些数据进行排序组合,汇总后取数的原则,来选出我们需要的数据。
当然,说了这么多,其实怎么写一段SQL,还是要看需求和数据结构。具体的数据结构和具体的需求,定位了一段SQL该怎么写。多实践,你就会感悟到了
⑥ 《数据仓库工具箱》读书笔记(一):维度建模初步
1、方便地保存数据
2、数据一致性
3、适应变化
4、及时展现数据
5、信息安全
6、数据权威
7、支撑业务
1、理解业务 理解用户
2、为用户提供高质量、相关的、可访问的信息
3、维护数仓/分析环境
1、维度模型和3NF模型包含的数据是一样的,只是维度模型存储的数据更易理解,查询性能更高,包装得更灵活
事实表:
2、维度模型中的事实表来自对业务过程性能的 度量
3、事实表中每行对应一个度量事件
4、每行中的数据是一个特定级别的细节数据,称为 粒度
5、事实表通常分为事务、累计快照、周期快照
6、事实表主键通常成为组合键
维度表:
7、维度表包含与业务过程度量事件有关的文本 环境
8、数仓分析环境取决于维度属性的质量和深度
1、Kimball
1、收集业务需求与数据实现
2、维度设计过程:选择业务过程、声明粒度、确认维度、确认事实
3、业务过程是组织完成的操作型活动(订单、注册)
4、粒度:事务表里的每一行表示的是什么
5、维度:用于描述环境
6、事实:对业务过程进行度量
7、灵活扩展:事实粒度一致时可直接创建列,通过新的维度列关联维度至事实,可以在维度表上简历新列添加属性,可以使事实表粒度更原子化
1、事实表行对应一个度量事件
2、可加、半可加是针对维度而言的,部分维度可加的是半可加。
3、事实表中的外键不能存在空值
4、最好保证事实度量是一致的
5、事务事实表:一行对应空间或时间上某点的度量事件,比如订单表、日志表
6、周期快照事实表:每行汇总了发生在某一周期的多个度量事件,比如一个用户在一天里的点击、退出次数
7、累计快照事实表:每行汇总了发生在过程开始和结束之间可预测步骤内的度量事件,比如订单有提单、支付、成单、配送、评价的可作为度量的过程
8、无事实事务表:可能存在某些事件仅仅记录 多维实体 ,没有数字化的事实
9、聚集事实表:对原子粒度事实表数据进行上卷
感觉多数还是事务和聚集事实表
1、维度表应当具有单一主键列,它是扁平非规范表
2、维度表需要主键,可以为维度表生成无语义的整数型主键,可以借助UDF来进行生成
3、操作型系统中自然键不能满足需求时可以采用持久性超自然键
4、将常用维度退化到事实表中,清楚地表明没有关联的维度
5、同一维度可能存在不同的层次,一级城市,二级城市
6、可以建立将不同维度合并到一起的杂项维度,而不要为每个标识或属性定义不同维度
7、 雪花维度: 低粒度属性作为辅助表通过属性键连接到基本维度,当这一过程中包含多重维度表层次时,建立的多级层次结构被称为雪花模式
8、支架维度:被引用的辅助维度成为支架维度,比如银行账户维度可以引用开户日期维度
当不同的维度表的属性具有相同列名和领域内容时,称维度表具有一致性
1、原样保留
2、重写
3、增加行
4、增加新属性(列)
1、固定深度位置层次,能够提佛那个可预测的、快速的查询性能
2、其他还可能存在可变深度层次、层次桥接、路径字符属性可变深度层次,但这些最好向固定深度层次进行统一
1、蜈蚣事实表:存在多层次维度外键
2、事实表也可分配代理键
3、多遍SQL以避免事实表间的连接
1、聚集事实也可作为维度进行处理(例如金额大于多少的用户)
2、步骤维度:在日志表里可以为行为顺序进行编号,探究行为发生的过程,这个维度叫步骤维度
⑦ 数据库粒度问题
粒度问题是设计数据仓库的一个最重要方面。粒度是指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。数据的粒度一直是一个设计问题。在早期建立的操作型系统中,粒度是用于访问授权的。当详细的数据被更新时,几乎总是把它存放在最低粒度级上。但在数据仓库环境中,对粒度不作假设。在数据仓库环境中粒度之所以是主要的设计问题,是因为它深深地影响存放在数据仓库中的数据量的大小,同时影响数据仓库所能回答的查询类型。在数据仓库中的数据量大小与查询的详细程度之间要作出权衡。
⑧ 数据量大,维度多怎么sql做查询,
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
若要提高效率,可以考虑全文检索。
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
13.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
⑨ sql 怎么取不重复的数据的所有数据
SQL数据重复分几种情况,一种是原数据重复,第二种是粒度重复,第三种是分布重复。
原数据重复的情况,你直接可以distinct掉,例如,学生表当中有两个重复的学号,你想取出不重复的,直接可以写:select
distinct
学号
from
学生表
第二种是查询粒度重复,比如你有一张表是存储区域的,分别为省、市、县三列。而你需要的是只查找不同的省市,则也可以使用distinct:select
distinct
省,市
from
区域
第三种则是分布重复,比如在join
的时候,左右两个表格存在一对多的关系,造成的重复,或者在聚合之后出现了维度重复,则这种相对来说比较麻烦,你需要在子查询中统计或查找出唯一值,然后再去关联,或者是按照一定的数据需求的取数规则,在查询结果后再进行聚合,取到唯一值。
不过不管怎么样,都是要看实际需求是什么样子的。大多可以用子查询和关联联合解决。