数仓--拉链表(原理、设计以及在Hive中的实现)
拉链表
- 什么是拉链表?
- 谈到拉链表就不得不谈SCD(缓慢变化维问题)
- 缓慢变化维怎么解决?(粗看有五种)
- 保留初始值(不让改)
- 改写属性值
- ==增加维度新行==
- 增加维度新列
- 使用历史表
- 举一个具体的应用场景,来设计并实现一份拉链表,最后并通过一些例子说明如何使用我们设计的这张表(因为现在Hive的大规模使用,我们会以Hive场景下的设计为例)。
- 拉链表的使用场景
- 为什么使用拉链表
- 拉链表的设计和实现
- 在Hive中实现拉链表
- ods层的user表
- ods层的user_update表
- 拉链表
- 实现sql语句
- 拉链表和流水表
- 查询性能
- 拉链表回滚
- 总结
什么是拉链表?
所谓拉链,就是记录历史
- 记录一个事物
从开始,一直到当前状态
的所有变化的信息。 - 我们可以使用这张表拿到最新的
当天的最新数据
以及之前的历史数据
。 - 既能满足反应数据的历史状态,又可以最大限度地节省存储空间
谈到拉链表就不得不谈SCD(缓慢变化维问题)
- 缓慢变化维,简称SCD(Slowly Changing Dimensions)
一些维度表的数据不是静态的,而是会随着时间而缓慢地变化
(这里的缓慢是相对事实表而言
,事实表数据变化的速度比维度表快)- 这种随着时间发生变化的维度称之为缓慢变化维
- 把处理维度表数据历史变化的问题,称为缓慢变化维问题,简称SCD问题
- 举例
用户ID 用户名 出生日期 住址
114 张三 1988-09-08 北京市朝阳区
- 这个用户的数据不是一直不变,而是有可能发生变化。例如:用户修改了出生日期 或者用户修改了住址。
缓慢变化维怎么解决?(粗看有五种)
保留初始值(不让改)
用户体验不好
- 出生日期的数据,始终按照用户第一次填写的数据为准
改写属性值
- 对其相应需要重写维度行中的旧值,以当前值替换。因此其始终反映最近的情况。
- 当一个维度值的数据源发生变化,并且不需要在维度表中保留变化历史时,通常用新数据来覆盖旧数据。这样的处理使属性所反映的中是最新的赋值。
这种方法有个前提,用户不关心这个数据的变化。
- 用户需要历史数据怎么办
- 我要分析历史变化数据怎么办
增加维度新行
典型代表就是拉链表。
- 用户维度表
修改前:
用户ID 用户ID 用户名 出生日期 住址
9527 114 张三 1988-09-08 北京市朝阳区
修改后:
编号 用户ID 用户名 出生日期 住址
9527 114 张三 1988-09-08 北京市朝阳区
9528 114 张三 1992-09-08 北京市海淀区
增加维度新列
- 用不同的字段来保存不同的值,就是在表中增加一个字段,这个字段用来保存变化后的当前值,而原来的值则被称为变化前的值。总的来说,这种方法通过添加字段来保存变化后的痕迹。
用户改多少列我要增加多少列
- 修改前:
编号 用户ID 用户名 出生日期 住址
9527 114 张三 1988-09-08 北京市朝阳区
修改后
编号 用户ID 用户名 出生日期 出生日期2 住址 现住址
9527 114 张三 1988-09-08 1992-09-08 北京市朝阳区 北京市海淀区
使用历史表
只保留最后一次的变化信息
举一个具体的应用场景,来设计并实现一份拉链表,最后并通过一些例子说明如何使用我们设计的这张表(因为现在Hive的大规模使用,我们会以Hive场景下的设计为例)。
- 我们要确定拉链表的时间粒度,比如说拉链表每天只取一个状态,也就是说如果一天有3个状态变更,我们只取最后一个状态,这种天粒度的表其实已经能解决大部分的问题了。
- 我们先看一个示例,这就是一张拉链表,存储的是用户的最基本信息以及每条记录的生命周期。我们可以使用这张表拿到最新的当天的最新数据以及之前的历史数据。
拉链表的使用场景
- 在数据仓库的数据模型设计过程中,经常会遇到下面这种表的设计:
- 有一些表的数据量很大,比如一张用户表,大约10亿条记录,50个字段,这种表,即使使用ORC压缩,单张表的存储也会超过100G,在HDFS使用双备份或者三备份的话就更大一些。
- 表中的部分字段会被update更新操作,如用户联系方式,产品的描述信息,订单的状态等等。
- 需要查看某一个时间点或者时间段的历史快照信息,比如,查看某一个订单在历史某一个时间点的状态。
- 表中的记录变化的比例和频率不是很大,比如,总共有10亿的用户,每天新增和发生变化的有200万左右,变化的比例占的很小。
- 那么对于这种表我该如何设计呢?下面有几种方案可选:
- 方案一:每天只留最新的一份,比如我们每天用Sqoop抽取最新的一份全量数据到Hive中。
- 方案二:每天保留一份全量的切片数据。
- 方案三:使用拉链表。
为什么使用拉链表
现在我们对前面提到的三种进行逐个的分析。
方案一
这种方案就不用多说了,实现起来很简单,每天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
说明
- t_start_date表示该条记录的生命周期开始时间,t_end_date表示该条记录的生命周期结束时间。
- t_end_date = '9999-12-31’表示该条记录目前处于有效状态。
- 如果查询当前所有有效的记录,则select * from user where t_end_date = ‘9999-12-31’。
- 如果查询2017-01-02的历史快照,则select * from user where t_start_date <= ‘2017-01-02’ and t_end_date >= ‘2017-01-02’。(此处要好好理解,是拉链表比较重要的一块。)
在Hive中实现拉链表
- 在现在的大数据场景下,大部分的公司都会选择以Hdfs和Hive为主的数据仓库架构。目前的Hdfs版本来讲,其文件系统中的文件是不能做改变的,也就是说Hive的表智能进行删除和添加操作,而不能进行update。基于这个前提,我们来实现拉链表。
- 还是以上面的用户表为例,我们要实现用户的拉链表。在实现它之前,我们需要先确定一下我们有哪些数据源可以用。
- 我们需要一张ODS层的用户全量表。至少需要用它来初始化。
- 每日的用户更新表。
- 而且我们要
确定拉链表的时间粒度,比如说拉链表每天只取一个状态
,也就是说如果一天有3个状态变更,我们只取最后一个状态,这种天粒度的表其实已经能解决大部分的问题了。 - 另外,补充一下每日的用户更新表该怎么获取,据笔者的经验,有3种方式拿到或者间接拿到每日的用户增量,因为它比较重要,所以详细说明:
- 我们可以监听Mysql数据的变化,比如说用Canal,最后合并每日的变化,获取到最后的一个状态。
- 假设我们每天都会获得一份切片数据,我们可以通过取两天切片数据的不同来作为每日更新表,这种情况下我们可以对所有的字段先进行concat,再取md5,这样就ok了。
- 流水表!有每日的变更流水表。
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,CASEWHEN A.t_end_time = '9999-12-31' AND B.user_num IS NOT NULL THEN '2017-01-01'ELSE A.t_end_timeEND AS t_end_timeFROM dws.user_his AS ALEFT JOIN ods.user_update AS BON A.user_num = B.user_num
UNIONSELECT C.user_num,C.mobile,C.reg_date,'2017-01-02' AS t_start_time,'9999-12-31' AS t_end_timeFROM ods.user_update AS C
) AS T
- 如感兴趣可以参考https://blog.csdn.net/qq_46893497/article/details/113965328
拉链表和流水表
流水表存放的是一个用户的变更记录,比如在一张流水表中,一天的数据中,会存放一个用户的每条修改记录,但是在拉链表中只有一条记录。
这是拉链表设计时需要注意的一个粒度问题。我们当然也可以设置的粒度更小一些,一般按天就足够。
查询性能
- 拉链表当然也会遇到查询性能的问题,比如说我们存放了5年的拉链数据,那么这张表势必会比较大,当查询的时候性能就比较低了,个人认为两个思路来解决:
- 在一些查询引擎中,我们对start_date和end_date做索引,这样能提高不少性能。
保留部分历史数据,比如说我们一张表里面存放全量的拉链表数据,然后再对外暴露一张只提供近3个月数据的拉链表
。
拉链表回滚
- 修正拉链表回滚问题本质就是:
- 就是找到历史的快照。
- 历史的快照可以根据起始更新时间,那你就找endtime小于你出错的数据就行了,出错日期的数据就行了。
- 重新导入数据,将原始拉链表数据过滤到指定日期之前即可。
举例:
拉链表dwd_userinfo_db,目前时间是2020-12-15,想回滚到2020-11-27,那么拉链表的状态得是2020-11-26userid starttime endtime
1 2020-11-12 2020-11-26
1 2020-11-27 9999-99-99
2 2020-11-16 2020-12-13
2 2020-12-14 9999-99-99拉链表回滚:过滤starttime<=2020-11-26的数据,将endtime>=2020-11-26的修改为9999-99-99insert overwrite table dwd_userinfo_db
selectuserid,starttime,if(endtime>=2020-11-26,'9999-99-99',endtime)
from dwd_userinfo_db
where starttime<=2020-11-26
总结
拉链表不存储冗余的数据,只有某行的数据发生变化,才需要保存下来,相比每次全量同步会节省存储空间
能够查询到历史快照
额外的增加了两列(dw_start_date dw_end_date),为数据行的生命周期
- 使用拉链表的时候可以不加t_end_date,即失效日期,但是加上之后,能优化很多查询。
可以加上当前行状态标识,能快速定位到当前状态。
- 在拉链表的设计中可以加一些内容,因为我们每天保存一个状态,如果我们在这个状态里面加一个字段,比如如当天修改次数,那么拉链表的作用就会更大。
参考自数据仓库之拉链表(原理、设计以及在Hive中的实现)
数仓--拉链表(原理、设计以及在Hive中的实现)相关推荐
- 数仓潮汐猎人 | 数据仓库企业数仓拉链表制作
拉链表 拉链表是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史.记录一个事物从开始,一直到当前状态的所有变化的信息. 下面就是一张拉链表,存储的是用户的最基本信息以及每条 ...
- 数仓拉链表使用_如何用拉链炸弹捍卫您的网站
数仓拉链表使用 This article was originally published on Christian's blog and republished here with his perm ...
- 数仓--拉链表实战⭐⭐⭐⭐⭐
拉链表实战⭐⭐⭐⭐⭐ 需求: 环境准备 在mysql中创建表和加载数据 在Hive中创建数据库和表: 全量导入数据到拉链表 将ODS层数据同步到DW层 增量导入数据到拉链表 在MySQL中添加增加数据 ...
- Hive数仓拉链表详解
文章目录 1. 初始化数据 1.1 建表 1.2 加载数据 1.3 验证同步数据 2. 新增2020-06-21分区数据 3. 加载数据到拉链表 4. 新增2020-06-22分区数据 5. 再次加载 ...
- hive中实现行转列_漫谈数据仓库之拉链表(原理、设计以及在Hive中的实现)
全文由下面几个部分组成: 先分享一下拉链表的用途.什么是拉链表. 通过一些小的使用场景来对拉链表做近一步的阐释,以及拉链表和常用的切片表的区别. 举一个具体的应用场景,来设计并实现一份拉链表,最后并通 ...
- Hive数仓之拉链表(原理、设计以及在Hive中的实现)
正 文 0x00 前言 本文将会谈一谈在数据仓库中拉链表相关的内容,包括它的原理.设计.以及在我们大数据场景下的实现方式. 全文由下面几个部分组成: 先分享一下拉链表的用途.什么是拉链表. 通过一些 ...
- 数仓02-从0到1设计和搭建
数仓学习的特点 理论庞杂,跟业务紧密关联 分层建模 数仓架构 类似于盖房子,有专业的土木.建筑.法规等,但每个房子都是独一无二的,跟房子的需求紧密结合. 注重实践,初级接触不到 技术.组件没有统一标准 ...
- 数仓(三):分层设计 ODS-DWD-DWS-ADS
数据仓库数据模型设计是构建数据仓库的核心过程之一.其目的是将多个数据源中的数据整合到一个统一的数据模型中,以支持业务分析和决策.然而,在数仓建设的过程中,由于未能完全按照规范操作, 从而导致数据仓库建 ...
- 数仓主题表怎么设计_陶瓷企业展厅设计主题风格怎么确定?
陶瓷是文化的结晶,艺术的精华.陶瓷的发明,是人类社会发展史上划时代的标志.当前,越来越多的陶瓷企业开始通过打造数字展厅来树立品牌形象,这一便捷的宣传推广形式,向消费者传达着陶瓷品牌个性.那么,在陶瓷企 ...
最新文章
- Uber AI简单方法实现大规模语言模型的精细控制
- 网站优化中受欢迎的文章是怎样的?
- escape与encodeURI、encodeURIComponent的区别
- keyshot详细安装教程
- 当SRS遇到K8s:如何实现高可用、回滚与灰度发布?
- 如何正确使用Node.js中的事件
- 电子公文的七大问题与八大关系(转)
- mysql数据库原理及应用章节答案_mysql数据库原理及应用答案
- 南京大学俞扬教授:强化学习真实环境不好用?那就模拟器来凑!
- matlab 一维 平滑,MATLAB中数据平滑处理
- mysql yum 安装
- 用定量测定葡萄糖的方法检测根管微渗漏-供应海藻酸钠修饰聚丙烯酸Alg-PAA 醛基化海藻酸钠(ASA) 海藻酸钠-多聚鸟氨酸-海藻酸(A-PLO-A)定制
- word图文混排复制到JMEditor图片不显示
- Grubbs检验离群值——循环算法求出多个离群值
- 在idea中使用mysql(超详细)
- 区块链数据的安全管理技术初探
- 【kratos】kratos 框架使用新的,还是非常好的技术框架,还要继续学习,掌握了proto 文件的使用、wire的使用就方便了。
- [概念] 价值分析(Value Analysis)/价值工程 (Value Engineering)
- 十、JVM调优实战——ES集群服务不稳定
- vue项目中使用element-ui中的el-calendar日历制作考勤