拉链表
历史拉链表是一种数据模型,主要是针对数据仓库设计中表存储数据的方式而定义的。所谓历史拉链表,就是指记录一个事物从开始一直到当前状态的所有变化信息。拉所有记录链表可以避免按每一天存储造成的海量存储问题,同时也是处理缓慢变化数据的一种常见方式
一、应用场景
现假设有如下场景:
一个企业拥有5000万会员信息,每天有20万会员资料变更,需要在数仓中记录会员表的历史变化以备分析使用,即每天都要保留一个快照供查询,反映历史数据的情况。在此场景中,需要反映5000万会员的历史变化,如果保留快照,存储两年就需要2X365X5000W条数据存储空间,数据量为365亿,如果存储更长时间,则无法估计需要的存储空间。而利用拉链算法存储,每日只向历史表中添加新增和变化的数据,每日不过20万条,存储4年也只需要3亿存储空间。
二、实现步骤
在拉链表中,每一条数据都有一个生效日期(effective_date)和失效日期(expire_date)。假设在一个用户表中,在2019年11月8日新增了两个用户,如下表所示,则这两条记录的生效时间为当天,由于到2019年11月8日为止,这两条就还没有被修改过,所以失效时间为一个给定的比较大的值,比如:3000-12-31。
member_id | phoneno | create_time | update_time |
---|---|---|---|
10001 | 13300000001 | 2019-11-08 | 3000-12-31 |
10002 | 13500000002 | 2019-11-08 | 3000-12-31 |
第二天(2019-11-09),用户10001被删除了,用户10002的电话号码被修改成13600000002.为了保留历史状态,用户10001的失效时间被修改为2019-11-09,用户10002则变成了两条记录,如下表所示:
member_id | phoneno | create_time | update_time |
---|---|---|---|
10001 | 13300000001 | 2019-11-08 | 2019-11-09 |
10002 | 13500000002 | 2019-11-08 | 2019-11-09 |
10002 | 13600000002 | 2019-11-09 | 3000-12-31 |
第三天(2019-11-10),又新增了用户10003,则用户表数据如小表所示:
member_id | phoneno | create_time | update_time |
---|---|---|---|
10001 | 13300000001 | 2019-11-08 | 2019-11-09 |
10002 | 13500000002 | 2019-11-08 | 2019-11-09 |
10002 | 13600000002 | 2019-11-09 | 3000-12-31 |
10003 | 13600000006 | 2019-11-10 | 3000-12-31 |
如果要查询最新的数据,那么只要查询失效时间为3000-12-31的数据即可,如果要查11月8号的历史数据,则筛选生效时间<= 2019-11-08并且失效时间>2019-11-08的数据即可。如果查询11月9号的数据,那么筛选条件则是生效时间<=2019-11-09并且失效时间>2019-11-09.
三、表结构
MySQL源member表:
CREATE TABLE member( |
ODS层增量表member_delta,每天一个分区:
CREATE TABLE member_delta( |
临时表:
CREATE TABLE member_his_tmp( |
DW层历史拉链表:
CREATE TABLE member_his( |
四、Demo数据准备
2019-11-08的数据为:
member_id | phoneno | create_time | update_time |
10001 | 13500000001 | 2019-11-08 14:47:55 | 2019-11-08 14:47:55 |
10002 | 13500000002 | 2019-11-08 14:48:33 | 2019-11-08 14:48:33 |
10003 | 13500000003 | 2019-11-08 14:48:53 | 2019-11-08 14:48:53 |
10004 | 13500000004 | 2019-11-08 14:49:02 | 2019-11-08 14:49:02 |
2019-11-09的数据为:其中蓝色代表新增数据,红色代表修改的数据:
member_id | phoneno | create_time | update_time |
10001 | 13500000001 | 2019-11-08 14:47:55 | 2019-11-08 14:47:55 |
10002 | 13600000002 | 2019-11-08 14:48:33 | 2019-11-09 14:48:33 |
10003 | 13500000003 | 2019-11-08 14:48:53 | 2019-11-08 14:48:53 |
10004 | 13500000004 | 2019-11-08 14:49:02 | 2019-11-08 14:49:02 |
10005 | 13500000005 | 2019-11-09 08:54:03 | 2019-11-09 08:54:03 |
10006 | 13500000006 | 2019-11-09 09:54:25 | 2019-11-09 09:54:25 |
2019-11-10的数据:其中蓝色代表新增数据,红色代表修改的数据:
member_id | phoneno | create_time | update_time |
10001 | 13500000001 | 2019-11-08 14:47:55 | 2019-11-08 14:47:55 |
10002 | 13600000002 | 2019-11-08 14:48:33 | 2019-11-09 14:48:33 |
10003 | 13500000003 | 2019-11-08 14:48:53 | 2019-11-08 14:48:53 |
10004 | 13600000004 | 2019-11-08 14:49:02 | 2019-11-10 14:49:02 |
10005 | 13500000005 | 2019-11-09 08:54:03 | 2019-11-09 08:54:03 |
10006 | 13500000006 | 2019-11-09 09:54:25 | 2019-11-09 09:54:25 |
10007 | 13500000007 | 2019-11-10 17:41:49 | 2019-11-10 17:41:49 |
五、全量初始装载
在启用拉链表时,先对其进行初始装载,比如以2019-11-08为开始时间,那么将MySQL源表全量抽取到ODS层member_delta表的2018-11-08的分区中,然后初始装载DW层的拉链表member_his。
INSERT overwrite TABLE member_his |
查询初始的历史拉链表数据:
六、增量抽取数据
每天,从源系统member表中,将前一天的增量数据抽取到ODS层的增量数据表member_delta对应的分区中。这里的增量需要通过member表中的创建时间和修改时间来确定,或者使用sqoop job监控update时间来进行增联抽取。比如,本案例中2019-11-09和2019-11-10为两个分区,分别存储了2019-11-09和2019-11-10日的增量数据。
2019-11-09分区的数据为:
2019-11-10分区的数据为:
七、增量刷新历史拉链数据
2019-11-09增量刷新历史拉链表:
将数据放进临时表:
INSERT overwrite TABLE member_his_tmp |
将数据覆盖到历史拉链表:
INSERT overwrite TABLE member_his |
查看历史拉链表:
2019-11-10增量刷新历史拉链表
将数据放进临时表
INSERT overwrite TABLE member_his_tmp |
查看历史拉链表:
将以上脚本封装成shell调度的脚本:
!/bin/bash |