本文将会谈一谈在数据仓库中拉链表相关的内容,包括它的原理、设计、以及在咱们大数据场景下的实现方式。sql
全文由下面几个部分组成:数据库
拉链表是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史。记录一个事物从开始,一直到当前状态的全部变化的信息。架构
咱们先看一个示例,这就是一张拉链表,存储的是用户的最基本信息以及每条记录的生命周期。咱们可使用这张表拿到最新的当天的最新数据以及以前的历史数据。oop
注册日期 | 用户编号 | 手机号码 | t_start_date | t_end_date |
---|---|---|---|---|
2017-01-01 | 001 | 111111 | 2017-01-01 | 9999-12-31 |
2017-01-01 | 002 | 222222 | 2017-01-01 | 2017-01-01 |
2017-01-01 | 002 | 233333 | 2017-01-02 | 9999-12-31 |
2017-01-01 | 003 | 333333 | 2017-01-01 | 9999-12-31 |
2017-01-01 | 004 | 444444 | 2017-01-01 | 2017-01-01 |
2017-01-01 | 004 | 432432 | 2017-01-02 | 2017-01-02 |
2017-01-01 | 004 | 432432 | 2017-01-03 | 9999-12-31 |
2017-01-02 | 005 | 555555 | 2017-01-02 | 2017-01-02 |
2017-01-02 | 005 | 115115 | 2017-01-03 | 9999-12-31 |
2017-01-03 | 006 | 666666 | 2017-01-03 | 9999-12-31 |
咱们暂且不对这张表作细致的讲解,后文会专门来阐述怎么来设计、实现和使用它。性能
在数据仓库的数据模型设计过程当中,常常会遇到下面这种表的设计:大数据
那么对于这种表我该如何设计呢?下面有几种方案可选:优化
如今咱们对前面提到的三种进行逐个的分析。网站
方案一spa
这种方案就不用多说了,实现起来很简单,天天drop掉前一天的数据,从新抽一份最新的。设计
优势很明显,节省空间,一些普通的使用也很方便,不用在选择表的时候加一个时间分区什么的。
缺点一样明显,没有历史数据,先翻翻旧帐只能经过其它方式,好比从流水表里面抽。
方案二
天天一份全量的切片是一种比较稳妥的方案,并且历史数据也在。
缺点就是存储空间占用量太大太大了,若是对这边表天天都保留一份全量,那么每次全量中会保存不少不变的信息,对存储是极大的浪费,这点我感触仍是很深的......
固然咱们也能够作一些取舍,好比只保留近一个月的数据?可是,需求是无耻的,数据的生命周期不是咱们能彻底左右的。
拉链表
拉链表在使用上基本兼顾了咱们的需求。
首先它在空间上作了一个取舍,虽然说不像方案一那样占用量那么小,可是它每日的增量可能只有方案二的千分之一甚至是万分之一。
其实它能知足方案二所能知足的需求,既能获取最新的数据,也能添加筛选条件也获取历史的数据。
因此咱们仍是颇有必要来使用拉链表的。
下面咱们来举个栗子详细看一下拉链表。
咱们接上在《漫谈数据仓库之维度建模》中的电商网站的例子,如今以用户的拉链表来讲明。
咱们先看一下在Mysql关系型数据库里的user表中信息变化。
在2017-01-01这一天表中的数据是:
注册日期 | 用户编号 | 手机号码 |
---|---|---|
2017-01-01 | 001 | 111111 |
2017-01-01 | 002 | 222222 |
2017-01-01 | 003 | 333333 |
2017-01-01 | 004 | 444444 |
在2017-01-02这一天表中的数据是, 用户002和004资料进行了修改,005是新增用户:
注册日期 | 用户编号 | 手机号码 | 备注 |
---|---|---|---|
2017-01-01 | 001 | 111111 | |
2017-01-01 | 002 | 233333 | (由222222变成233333) |
2017-01-01 | 003 | 333333 | |
2017-01-01 | 004 | 432432 | (由444444变成432432) |
2017-01-02 | 005 | 555555 | (2017-01-02新增) |
在2017-01-03这一天表中的数据是, 用户004和005资料进行了修改,006是新增用户:
注册日期 | 用户编号 | 手机号码 | 备注 |
---|---|---|---|
2017-01-01 | 001 | 111111 | |
2017-01-01 | 002 | 233333 | |
2017-01-01 | 003 | 333333 | |
2017-01-01 | 004 | 654321 | (由432432变成654321) |
2017-01-02 | 005 | 115115 | (由555555变成115115) |
2017-01-03 | 006 | 666666 | (2017-01-03新增) |
若是在数据仓库中设计成历史拉链表保存该表,则会有下面这样一张表,这是最新一天(即2017-01-03)的数据:
注册日期 | 用户编号 | 手机号码 | t_start_date | t_end_date |
---|---|---|---|---|
2017-01-01 | 001 | 111111 | 2017-01-01 | 9999-12-31 |
2017-01-01 | 002 | 222222 | 2017-01-01 | 2017-01-01 |
2017-01-01 | 002 | 233333 | 2017-01-02 | 9999-12-31 |
2017-01-01 | 003 | 333333 | 2017-01-01 | 9999-12-31 |
2017-01-01 | 004 | 444444 | 2017-01-01 | 2017-01-01 |
2017-01-01 | 004 | 432432 | 2017-01-02 | 2017-01-02 |
2017-01-01 | 004 | 654321 | 2017-01-03 | 9999-12-31 |
2017-01-02 | 005 | 555555 | 2017-01-02 | 2017-01-02 |
2017-01-02 | 005 | 115115 | 2017-01-03 | 9999-12-31 |
2017-01-03 | 006 | 666666 | 2017-01-03 | 9999-12-31 |
说明
在如今的大数据场景下,大部分的公司都会选择以Hdfs和Hive为主的数据仓库架构。目前的Hdfs版原本讲,其文件系统中的文件是不能作改变的,也就是说Hive的表智能进行删除和添加操做,而不能进行update。基于这个前提,咱们来实现拉链表。
仍是以上面的用户表为例,咱们要实现用户的拉链表。在实现它以前,咱们须要先肯定一下咱们有哪些数据源能够用。
并且咱们要肯定拉链表的时间粒度,好比说拉链表天天只取一个状态,也就是说若是一天有3个状态变动,咱们只取最后一个状态,这种天粒度的表其实已经能解决大部分的问题了。
另外,补充一下每日的用户更新表该怎么获取,据笔者的经验,有3种方式拿到或者间接拿到每日的用户增量,由于它比较重要,因此详细说明:
ods层的user表
如今咱们来看一下咱们ods层的用户资料切片表的结构:
CREATE EXTERNAL TABLE ods.user ( user_num STRING COMMENT '用户编号', mobile STRING COMMENT '手机号码', reg_date STRING COMMENT '注册日期' COMMENT '用户资料表' PARTITIONED BY (dt string) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' STORED AS ORC LOCATION '/ods/user'; )
ods层的user_update表
而后咱们还须要一张用户每日更新表,前面已经分析过该若是获得这张表,如今咱们假设它已经存在。
CREATE EXTERNAL TABLE ods.user_update ( user_num STRING COMMENT '用户编号', mobile STRING COMMENT '手机号码', reg_date STRING COMMENT '注册日期' COMMENT '每日用户资料更新表' PARTITIONED BY (dt string) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' STORED AS ORC LOCATION '/ods/user_update'; )
拉链表
如今咱们建立一张拉链表:
CREATE EXTERNAL TABLE dws.user_his ( user_num STRING COMMENT '用户编号', mobile STRING COMMENT '手机号码', reg_date STRING COMMENT '用户编号', t_start_date , t_end_date COMMENT '用户资料拉链表' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' STORED AS ORC LOCATION '/dws/user_his'; )
实现sql语句
而后初始化的sql就不写了,其实就至关因而拿一天的ods层用户表过来就行,咱们写一下每日的更新语句。
如今咱们假设咱们已经已经初始化了2017-01-01的日期,而后须要更新2017-01-02那一天的数据,咱们有了下面的Sql。
而后把两个日期设置为变量就能够了。
INSERT OVERWRITE TABLE dws.user_his SELECT * FROM ( SELECT A.user_num, A.mobile, A.reg_date, A.t_start_time, CASE WHEN A.t_end_time = '9999-12-31' AND B.user_num IS NOT NULL THEN '2017-01-01' ELSE A.t_end_time END AS t_end_time FROM dws.user_his AS A LEFT JOIN ods.user_update AS B ON A.user_num = B.user_num UNION SELECT C.user_num, C.mobile, C.reg_date, '2017-01-02' AS t_start_time, '9999-12-31' AS t_end_time FROM ods.user_update AS C ) AS T
好了,咱们分析了拉链表的原理、设计思路、而且在Hive环境下实现了一份拉链表,下面对拉链表作一些小的补充。
流水表存放的是一个用户的变动记录,好比在一张流水表中,一天的数据中,会存放一个用户的每条修改记录,可是在拉链表中只有一条记录。
这是拉链表设计时须要注意的一个粒度问题。咱们固然也能够设置的粒度更小一些,通常按天就足够。
拉链表固然也会遇到查询性能的问题,好比说咱们存放了5年的拉链数据,那么这张表势必会比较大,当查询的时候性能就比较低了,我的认为两个思路来解决:
咱们在这篇文章里面详细地分享了一下和拉链表相关的知识点,可是仍然会有一会遗漏。欢迎交流。
在后面的使用中又有了一些心得,补充进来:
使用拉链表的时候能够不加t_end_date,即失效日期,可是加上以后,能优化不少查询。
能够加上当前行状态标识,能快速定位到当前状态。
在拉链表的设计中能够加一些内容,由于咱们天天保存一个状态,若是咱们在这个状态里面加一个字段,好比如当天修改次数,那么拉链表的做用就会更大。