拉链表

34 篇文章 1 订阅
31 篇文章 2 订阅

http://pylyria.com/2018/07/03/%E6%8B%89%E9%93%BE%E8%A1%A8%E5%8F%8A%E5%85%B6Hive%E5%AE%9E%E7%8E%B0/

 

 

Note:

需要三张表:初始全量表、拉链表、增量表(存储发生变化数据的表)

第一步:创建一个 跟全量表一样表结构的  拉链表,并把全量表数据放到拉链表中,但是设置 拉链表中初始数据的end_date 字段为9999-99-99

第二步:根据增量表来更新 拉链表。

主要分为两部分:     1.把拉链表和增量表关联,关闭在增量表中出现的数据的end_date 为昨日日期,(即因为这些数据已经在增量表中出现,说明这些数据已经在昨天发生了变化,so 这些数据的end_date 要改变为昨日)

2.把增量表中的数据,union all  到拉链表中去,并设置 这些数据的end_date 字段为 999-99-99,(因为这些数据是最新变化的,即它们的状态是最新的,应该为9999-99-99)

===========================================

简介

本文介绍数据仓库技术中拉链表相关的内容,包括其原理、设计、适用场景以及在Hive中的实现方式。

拉链表是什么

拉链表是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史。记录一个事物从开始,一直到当前状态的所有变化的信息。即可以在所规定的时间粒度上体现数据完整的生命周期。

以下为一个拉链表的示例,存储账户基本信息和每条记录的生命周期,我们可以拿到每一个账户的历史上任意一天的状态和余额信息。

账户ID账户余额账户状态开始时间结束时间
1100正常2017-12-312018-01-01
2200正常2017-12-312018-01-01
3500正常2017-12-312018-01-01
1900正常2018-01-012018-03-01
30注销2018-01-319999-99-99
1500正常2018-03-012018-03-20
1300正常2018-03-209999-99-99
2800正常2018-01-019999-99-99

拉链表的适用场景

在数据仓库的数据模型设计中,常常会遇到下列情况:

  1. 某个表存储某种类型数据多个维度的数据
  2. 某些维度会发生一些变化,但是发生变化的维度不固定,即大部分维度都有可能改变
  3. 变化频率不会特别高,按数据仓库建设的时间粒度来区分即可以感知到这种变化
  4. 业务有需求获取历史上某一个时间点的历史快照信息,例如:对过去某个时间的所有数据做统计

针对于这些情况,一般来说有以下几种方案可选:

  1. 每天删除历史数据,只导入一份最新的数据
  2. 每天抽取一份全量数据,按天存储历史数据
  3. 使用拉链表来设计

使用拉链表的原因

下面我们来针对以上三个方案逐个分析

方案1:

  • 优点:节省空间,使用方便,甚至都不用创建分区。
  • 缺点:没有历史数据,如果需要历史数据只能想办法通过流水表获取,逻辑较复杂。

方案2:

  • 优点:实现简单,最为稳妥。
  • 缺点:因为信息维度多但是变化维度不多,存储了大量的重复数据,对存储空间是极大的浪费。虽然可以做一些取舍只保留一段时间的数据,但是需求方是无耻的,这个时间段可能会逐渐被拉长,以至于必须永久保留数据。

方案3:

  • 优点:可以保留历史快照,且占用空间不大,数据冗余少。
  • 缺点:相比于前两种方案实现较为复杂,如果历史上有某一天数据出现问题则需要一个完整的回溯过程保证全表数据是正常的。

拉链表的逻辑设计

接下来通过一个实例来简述一下应该如何设计拉链表

首先,针对于某账户信息表,在2018年1月1日的信息如下表(为了简化设计,这里增加了信息变更时间UPDATE_DATE):

账户ID账户余额账户状态UPDATE_DATE
1100正常2018-01-01
2200正常2018-01-01
3500正常2018-01-01

由此表我们可以得到以下拉链表,开始时间和结束时间表示数据的生命周期,结束时间9999-99-99表示此条数据为当前时间的数据:

账户ID账户余额账户状态开始时间结束时间
1100正常2018-01-019999-99-99
2200正常2018-01-019999-99-99
3500正常2018-01-019999-99-99

接下来,在2018年1月2日做数据采集时,采集到了UPDATE_DATE为2018-01-02的以下数据:

账户ID账户余额账户状态UPDATE_DATE
1600正常2018-01-02
20注销2018-01-02
4100正常2018-01-02

通过两个表的对比可以得出,对于同一个账户ID来说,1号账户的账户余额发生变更变成了600,2号账户的余额发生变更变成了100,则我们可以根据这张表和上面的拉链表关联,得到新的拉链表:

账户ID账户余额账户状态开始时间结束时间
1100正常2018-01-012018-01-02
2200正常2018-01-012018-01-02
3500正常2018-01-019999-99-99
1100正常2018-01-029999-99-99
20注销2018-01-029999-99-99
4100正常2018-01-029999-99-99

以此类推,我们可以查询到2018年1月1日之后的所有生命周期的数据,例如:

  • 查询当前所有有效记录: SELECT * FROM ACCOUNT_HIST WHERE END_DATE = '9999-99-99'
  • 查询2018年1月1日的历史快照:SELECT * FROM ACCOUNT_HIST WHERE START_DATE <= '2018-01-01' AND END_DATE >= '2018-01-01'

拉链表的实现

关系型数据库实现

在关系型数据库中,因为做INSERT/UPDATE操作非常的方便,所以即使逐条将变化的数据与拉链表比较并且做变更,也是非常容易做到的。

当然考虑到性能原因,会借助一些ETL工具,把数据加载到ETL工具中做拉链的操作。

这些方式都已有很成熟的处理方式,本文不再赘述。

Hive实现

在现有大数据场景之下,很多公司会使用Hive作为数据仓库的底层架构,而Hive在大部分使用场景中又是以HDFS为底层存储的。而在现有的Hadoop版本中,HDFS只支持Append操作(据说Hadoop3之后的版本支持了Update,但是截止笔者发布此篇文章Hadoop的Stable版本仍然是2.9.1,所以Update操作待考证)。

基于这个前提,我们要在Hive上实现拉链表,就要需要通过一些技巧来更新全表的数据。

首先根据以上逻辑设计,我们必须通过某些方式获得以下两部分数据:

  • 一张全量表用于初始化
  • 保存当天的增量数据的增量表用于每日的拉链表的生成

全量表的获取不用多说,对于增量表的获取,根据经验主要有以下几种方式:

  • 监听上游数据库日志,获取数据库表的变化,并且在日终将记录下来的变化数据汇总成一份增量表
  • 假如每天都可以得到一份切片数据,那么我们可以比较两份切片数据差异的部分作为增量表,这里只要把这个表里所有的维度信息做CONCAT,然后再做MD5就可以比较出来了
  • 可以根据流水表与之前全量表的运算,把发生变化的部分当作当日的增量数据

下面我们来在Hive中分别建立全量表、增量表,为了简便默认已经有可以表示数据变化时间的UPDATE_DATE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--全量表
create table ods.account(
  id string comment '账户ID',
  balance string comment '账户余额',
  status string comment '账户状态',
  update_date string comment '数据变更时间'
)
comment '账户表'
store as textfile;

--增量表
create table ods.account_update(
  id string comment '账户ID',
  balance string comment '账户余额',
  status string comment '账户状态',
  update_date string comment '数据变更时间'
)
comment '每日增量账户表'
store as textfile;

根据全量表的表结构,在Hive中创建拉链表:

1
2
3
4
5
6
7
8
9
create table dw.account_hist(
  id string comment '账户ID',
  balance string comment '账户余额',
  status string comment '账户状态',
  start_date string comment '数据起始时间',
  end_date string comment '数据结束时间'
)
comment '账户拉链表'
store as textfile;

初始化这个拉链表的HiveQL语句如下:

1
2
3
4
insert overwrite table dw.account_hist
  select
    id, balance, status, '${TODAY}', '9999-99-99'
  from ods.account;

每日在获取增量表数据后,执行以下语句,即可得到当日的拉链表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
insert overwrite table dw.account_hist
  select * from
  (
    select 
      a.id, a.balance, a.status, a.start_date
      case
        when a.end_date = '9999-99-99' and b.id is not null then '${YESTERDAY}'
        else a.end_date
      end as end_date
    from dw.account_hist as a
    left outer join ods.account_update as b
    on b.id = a.id
    union all
    select
      a.id, a.balance, a.status, 
      ${TODAY} as start_date,
      '9999-99-99' as end_date
    from ods.account_update as a
  ) as t

只要每天执行上面这个脚本,那么拉链表就可以每天按计划生成了。

后记

拉链表的查询性能问题

因为在一个表里保存了所有历史快照,所以如果时间足够久的话,拉链表也会遇到查询性能问题,主要通过以下几种思路来优化查询:

  1. 因为基本上所有的查询都依赖于START_DATE和END_DATE,所以在关系型数据库中对这两个字段做索引是一种比较好的选择。而在Hive中,如果使用ORC的方式存储,可以考虑在这两个键上面做Bloom Filter Index和Row Group Index,并且在生成拉链表的时候对这两个键做SORT BY后再做INSERT。
  2. 按业务需求的时间范围拆分拉链表,一个拉链表保存所有历史数据,另一个更为常用的拉链表保存一段时间内的历史数据。
  3. 在拉链表的设计中新增一些内容,比如说修改次数,根据业务需求做一个拉链表只保留最近几次修改的数据,其他的所有历史数据在另一个拉链表存储。
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值