摘要:前言本文将会谈一谈在数据仓库中拉链表相关的内容,包括它的原理设计以及在我们大数据场景下的实现方式。补充好了,我们分析了拉链表的原理设计思路并且在环境下实现了一份拉链表,下面对拉链表做一些小的补充。这是拉链表设计时需要注意的一个粒度问题。
0x00 前言
本文将会谈一谈在数据仓库中拉链表相关的内容,包括它的原理、设计、以及在我们大数据场景下的实现方式。
全文由下面几个部分组成:
先分享一下拉链表的用途、什么是拉链表。
通过一些小的使用场景来对拉链表做近一步的阐释,以及拉链表和常用的切片表的区别。
举一个具体的应用场景,来设计并实现一份拉链表,最后并通过一些例子说明如何使用我们设计的这张表(因为现在Hive的大规模使用,我们会以Hive场景下的设计为例)。
分析一下拉链表的优缺点,并对前面的提到的一些内容进行补充说明,比如说拉链表和流水表的区别。
0x01 什么是拉链表拉链表是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史。记录一个事物从开始,一直到当前状态的所有变化的信息。
我们先看一个示例,这就是一张拉链表,存储的是用户的最基本信息以及每条记录的生命周期。我们可以使用这张表拿到最新的当天的最新数据以及之前的历史数据。
注册日期 | 用户编号 | 手机号码 | 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 |
我们暂且不对这张表做细致的讲解,后文会专门来阐述怎么来设计、实现和使用它。
拉链表的使用场景在数据仓库的数据模型设计过程中,经常会遇到下面这种表的设计:
有一些表的数据量很大,比如一张用户表,大约10亿条记录,50个字段,这种表,即使使用ORC压缩,单张表的存储也会超过100G,在HDFS使用双备份或者三备份的话就更大一些。
表中的部分字段会被update更新操作,如用户联系方式,产品的描述信息,订单的状态等等。
需要查看某一个时间点或者时间段的历史快照信息,比如,查看某一个订单在历史某一个时间点的状态。
表中的记录变化的比例和频率不是很大,比如,总共有10亿的用户,每天新增和发生变化的有200万左右,变化的比例占的很小。
那么对于这种表我该如何设计呢?下面有几种方案可选:
方案一:每天只留最新的一份,比如我们每天用Sqoop抽取最新的一份全量数据到Hive中。
方案二:每天保留一份全量的切片数据。
方案三:使用拉链表。
为什么使用拉链表现在我们对前面提到的三种进行逐个的分析。
方案一
这种方案就不用多说了,实现起来很简单,每天drop掉前一天的数据,重新抽一份最新的。
优点很明显,节省空间,一些普通的使用也很方便,不用在选择表的时候加一个时间分区什么的。
缺点同样明显,没有历史数据,先翻翻旧账只能通过其它方式,比如从流水表里面抽。
方案二
每天一份全量的切片是一种比较稳妥的方案,而且历史数据也在。
缺点就是存储空间占用量太大太大了,如果对这边表每天都保留一份全量,那么每次全量中会保存很多不变的信息,对存储是极大的浪费,这点我感触还是很深的......
当然我们也可以做一些取舍,比如只保留近一个月的数据?但是,需求是无耻的,数据的生命周期不是我们能完全左右的。
拉链表
拉链表在使用上基本兼顾了我们的需求。
首先它在空间上做了一个取舍,虽说不像方案一那样占用量那么小,但是它每日的增量可能只有方案二的千分之一甚至是万分之一。
其实它能满足方案二所能满足的需求,既能获取最新的数据,也能添加筛选条件也获取历史的数据。
所以我们还是很有必要来使用拉链表的。
0x02 拉链表的设计和实现 如何设计一张拉链表下面我们来举个栗子详细看一下拉链表。
我们接上在《漫谈数据仓库之维度建模》中的电商网站的例子,现在以用户的拉链表来说明。
我们先看一下在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 " " LINES TERMINATED BY " " 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 " " LINES TERMINATED BY " " 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 " " LINES TERMINATED BY " " 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 T0x03 补充
好了,我们分析了拉链表的原理、设计思路、并且在Hive环境下实现了一份拉链表,下面对拉链表做一些小的补充。
拉链表和流水表流水表存放的是一个用户的变更记录,比如在一张流水表中,一天的数据中,会存放一个用户的每条修改记录,但是在拉链表中只有一条记录。
这是拉链表设计时需要注意的一个粒度问题。我们当然也可以设置的粒度更小一些,一般按天就足够。
查询性能拉链表当然也会遇到查询性能的问题,比如说我们存放了5年的拉链数据,那么这张表势必会比较大,当查询的时候性能就比较低了,个人认为两个思路来解决:
在一些查询引擎中,我们对start_date和end_date做索引,这样能提高不少性能。
保留部分历史数据,比如说我们一张表里面存放全量的拉链表数据,然后再对外暴露一张只提供近3个月数据的拉链表。
0xFF 总结我们在这篇文章里面详细地分享了一下和拉链表相关的知识点,但是仍然会有一会遗漏。欢迎交流。
在后面的使用中又有了一些心得,补充进来:
使用拉链表的时候可以不加t_end_date,即失效日期,但是加上之后,能优化很多查询。
可以加上当前行状态标识,能快速定位到当前状态。
在拉链表的设计中可以加一些内容,因为我们每天保存一个状态,如果我们在这个状态里面加一个字段,比如如当天修改次数,那么拉链表的作用就会更大。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/8250.html
0x00 前言 即使没有数据倾斜,千亿级的数据查询对于系统也是一种巨大负担,对于数据开发来说,如何来优化它,既是挑战,也是机遇! 在上一篇文章 《漫谈千亿级数据优化实践:数据倾斜(纯干货)》中,我们分享了一些在千亿级数据优化实践中和数据倾斜相关的内容。本文将分享千亿级数据优化的另个一点:如何使用使用数据! 注意: 本文会限定一些业务场景和技术架构,因此解决方法会局限于此。很多问题可以通过换架构或...
摘要:各种数据建模方法,如维度建模。因此,下面的将详细地阐述数据建模中的典型代表维度建模,对它的的相关理论以及实际使用做深入的分析。详细介绍维度建模的基本概念以及相关理论。学过数据库的童鞋应该都知道星型模型,星型模型就是我们一种典型的维度模型。 0x00 前言 下面的内容,是笔者在学习和工作中的一些总结,其中概念性的内容大多来自书中,实践性的内容大多来自自己的工作和个人理解。由于资历尚浅,难...
摘要:中的数据倾斜主要表现在阶段卡在,一直不能结束。数据倾斜的原理一数据倾斜产生的原因我们以和的使用场景为例。另外千亿级别的数据还会有更多的难点,不仅仅是数据倾斜的问题,这一点在后面也会有专门的分享。 0x00 前言 数据倾斜是大数据领域绕不开的拦路虎,当你所需处理的数据量到达了上亿甚至是千亿条的时候,数据倾斜将是横在你面前一道巨大的坎。 迈的过去,将会海阔天空!迈不过去,就要做好准备:很...
摘要:而这些具有差异的数据进入数仓后需要整合在一起命名规范的统一。这些维度如果作为事实存在事实表中,则会导致事实表占用空间变大如果单独建立维表,则会出现许多零碎的小维表。 OneData 是阿里巴巴内部进行数据整合和管理方法体系和工具。指导思想首先,要进行充分的业务调研和需求分析。其次,进行数据总体架构设计,主要是根...
摘要:例如以研究人的旅游消费为主题的数据集中,便可以结合航空公司的登机出行信息,以及银联系统的刷卡记录,进行结合分析,产生数据集。 0x00 前言 最近出现了好几次同样的对话场景:问:你是做什么的?答:最近在搞数据仓库。问:哦,你是传统行业的吧,我是搞大数据的。答:...... 发个牢骚,搞大数据的也得建设数据仓库吧。而且不管是传统行业还是现在的互联网公司,都需要对数据仓库有一定的重视,而不...
阅读 1671·2021-11-17 09:33
阅读 1220·2019-08-30 15:44
阅读 1277·2019-08-29 18:42
阅读 312·2019-08-29 13:59
阅读 617·2019-08-28 17:58
阅读 2670·2019-08-26 12:02
阅读 2302·2019-08-23 18:40
阅读 2268·2019-08-23 18:13