1.什么是拉链表
拉链表是根据数据存储得方式而定义的,记录每天信息的生命周期,一旦一条记录的生命周期结束,就开始新的一条记录,并把当前日期作为生效日期。
2.为什么要做拉链表
- 有一些表的数据量很大,比如一张用户表,大约10亿条记录,50个字段,这种表,即使使用ORC压缩,单张表的存储也会超过100G,在HDFS使用双备份或者三备份的话就更大一些。
- 表中的部分字段会被update更新操作,如用户联系方式,产品的描述信息,订单的状态等等。
- 需要查看某一个时间点或者时间段的历史快照信息,比如,查看某一个订单在历史某一个时间点的状态。
- 表中的记录变化的比例和频率不是很大,比如,总共有10亿的用户,每天新增和发生变化的有200万左右,变化的比例占的很小。
那么对于这种表我该如何设计呢?下面有几种方案可选:
方案一:每天只留最新的一份,比如我们每天用Sqoop抽取最新的一份全量数据到Hive中。
方案二:每天保留一份全量的切片数据。
方案三:使用拉链表
方案一
这种方案就不用多说了,实现起来很简单,每天drop掉前一天的数据,重新抽一份最新的。
优点很明显,节省空间,一些普通的使用也很方便,不用在选择表的时候加一个时间分区什么的。
缺点同样明显,没有历史数据,先翻翻旧账只能通过其它方式,比如从流水表里面抽。
方案二
每天一份全量的切片是一种比较稳妥的方案,而且历史数据也在。
缺点就是存储空间占用量太大太大了,如果对这边表每天都保留一份全量,那么每次全量中会保存很多不变的信息,对存储是极大的浪费,这点我感触还是很深的......
当然我们也可以做一些取舍,比如只保留近一个月的数据?但是,需求是无耻的,数据的生命周期不是我们能完全左右的。
拉链表
拉链表在使用上基本兼顾了我们的需求。
首先它在空间上做了一个取舍,虽说不像方案一那样占用量那么小,但是它每日的增量可能只有方案二的千分之一甚至是万分之一。
其实它能满足方案二所能满足的需求,既能获取最新的数据,也能添加筛选条件也获取历史的数据。
3.如何做拉链表
拉链表制作流程图
4.拉链表制作
创建拉链表
1 drop table if exists dwd_order_info_his; 2 create external table dwd_order_info_his( 3 `id` string COMMENT '订单编号', 4 `total_amount` decimal(10,2) COMMENT '订单金额', 5 `order_status` string COMMENT '订单状态', 6 `user_id` string COMMENT '用户id' , 7 `payment_way` string COMMENT '支付方式', 8 `out_trade_no` string COMMENT '支付流水号', 9 `create_time` string COMMENT '创建时间', 10 `operate_time` string COMMENT '操作时间', 11 `start_date` string COMMENT '有效开始日期', 12 `end_date` string COMMENT '有效结束日期' 13 ) COMMENT '订单拉链表' 14 stored as parquet 15 location '/warehouse/gmall/dwd/dwd_order_info_his/' 16 tblproperties ("parquet.compression"="snappy");
初始化拉链表
1 insert overwrite table dwd_order_info_his 2 select 3 id, 4 total_amount, 5 order_status, 6 user_id, 7 payment_way, 8 out_trade_no, 9 create_time, 10 operate_time, 11 '2019-02-13', 12 '9999-99-99' 13 from ods_order_info oi 14 where oi.dt='2019-02-13';
如何获得每日变动表
(1)最好表内有创建时间和变动时间
(2)如果没有,可以利用第三方工具监控比如canal,监控MySQL的实时变化进行记录(麻烦)。
(3)逐行对比前后两天的数据, 检查md5(concat(全部有可能变化的字段))是否相同(low)
(4)要求业务数据库提供变动流水
创建临时表
1 drop table if exists dwd_order_info_his_tmp; 2 create table dwd_order_info_his_tmp( 3 `id` string COMMENT '订单编号', 4 `total_amount` decimal(10,2) COMMENT '订单金额', 5 `order_status` string COMMENT '订单状态', 6 `user_id` string COMMENT '用户id' , 7 `payment_way` string COMMENT '支付方式', 8 `out_trade_no` string COMMENT '支付流水号', 9 `create_time` string COMMENT '创建时间', 10 `operate_time` string COMMENT '操作时间', 11 `start_date` string COMMENT '有效开始日期', 12 `end_date` string COMMENT '有效结束日期' 13 ) COMMENT '订单拉链临时表' 14 stored as parquet 15 location '/warehouse/gmall/dwd/dwd_order_info_his_tmp/' 16 tblproperties ("parquet.compression"="snappy");
合并数据到临时表
//dwd_order_info为存储新增及变化的表
1 insert overwrite table dwd_order_info_his_tmp 2 select * from 3 ( 4 select 5 id, 6 total_amount, 7 order_status, 8 user_id, 9 payment_way, 10 out_trade_no, 11 create_time, 12 operate_time, 13 '2019-02-14' start_date, 14 '9999-99-99' end_date 15 from dwd_order_info where dt='2019-02-14' 16 17 union all 18 select oh.id, 19 oh.total_amount, 20 oh.order_status, 21 oh.user_id, 22 oh.payment_way, 23 oh.out_trade_no, 24 oh.create_time, 25 oh.operate_time, 26 oh.start_date, 27 if(oi.id is null, oh.end_date, date_add(oi.dt,-1)) end_date 28 from dwd_order_info_his oh left join 29 ( 30 select 31 * 32 from dwd_order_info 33 where dt='2019-02-14' 34 ) oi 35 on oh.id=oi.id and oh.end_date='9999-99-99' 36 )his 37 order by his.id, start_date;
覆盖原拉链表
insert overwrite table dwd_order_info_his
查询性能
拉链表当然也会遇到查询性能的问题,比如说我们存放了5年的拉链数据,那么这张表势必会比较大,当查询的时候性能就比较低了,个人认为两个思路来解决:
- 在一些查询引擎中,我们对start_date和end_date做索引,这样能提高不少性能。
- 保留部分历史数据,比如说我们一张表里面存放全量的拉链表数据,然后再对外暴露一张只提供近3个月数据的拉链表。
参考文章:https://www.jianshu.com/p/799252156379