數(shù)倉--DW--Hadoop數(shù)倉實踐Case-18-拉鏈表實踐

拉鏈表在數(shù)倉的實際開發(fā)中應(yīng)用廣泛,切實解決優(yōu)化存儲
重點是對變化的數(shù)據(jù)進行統(tǒng)一管理,和緩慢變化維的處理還是不一樣的。注意對比學(xué)習(xí)

拉鏈表概述

  • 拉鏈表是針對數(shù)據(jù)倉庫設(shè)計中表存儲數(shù)據(jù)的方式而定義的,顧名思義,所謂拉鏈,就是記錄歷史。記錄一個事物從開始,一直到當(dāng)前狀態(tài)的所有變化的信息。
  • 我們先看一個示例,這就是一張拉鏈表,存儲的是用戶的最基本信息以及每條記錄的生命周期。我們可以使用這張表拿到最新的當(dāng)天的最新數(shù)據(jù)以及之前的歷史數(shù)據(jù)。


    數(shù)倉拉鏈表示例.PNG
  • 從上圖中可以看出,用戶的手機號碼是會進行改變的??梢愿鶕?jù)開始日期和結(jié)束日期進行判斷。

拉鏈表使用場景

    1. 數(shù)據(jù)量比較大;
    1. 表中的部分字段會被update,如用戶的地址,產(chǎn)品的描述信息,訂單的狀態(tài)等等;
    1. 需要查看某一個時間點或者時間段的歷史快照信息,比如,查看某一個訂單在歷史某一個時間點的狀態(tài),比如,查看某一個用戶在過去某一段時間內(nèi),更新過幾次等等;
    1. 變化的比例和頻率不是很大,比如,總共有1000萬的會員,每天新增和發(fā)生變化的有10萬左右;
    1. 如果對這邊表每天都保留一份全量,那么每次全量中會保存很多不變的信息,對存儲是極大的浪費;
  • 拉鏈歷史表,既能滿足反應(yīng)數(shù)據(jù)的歷史狀態(tài),又可以最大程度的節(jié)省存儲;
  • 案例
    在數(shù)據(jù)倉庫的數(shù)據(jù)模型設(shè)計過程中,經(jīng)常會遇到下面這種表的設(shè)計:
    • 有一些表的數(shù)據(jù)量很大,比如一張用戶表,大約10億條記錄,50個字段,這種表,即使使用ORC壓縮,單張表的存儲也會超過100G,在HDFS使用雙備份或者三備份的話就更大一些。
    • 表中的部分字段會被update更新操作,如用戶聯(lián)系方式,產(chǎn)品的描述信息,訂單的狀態(tài)等等。
    • 需要查看某一個時間點或者時間段的歷史快照信息,比如,查看某一個訂單在歷史某一個時間點的狀態(tài)。
    • 表中的記錄變化的比例和頻率不是很大,比如,總共有10億的用戶,每天新增和發(fā)生變化的有200萬左右,變化的比例占的很小。

那么對于這種表我該如何設(shè)計呢?下面有幾種方案可選:

  • 方案一:每天只留最新的一份,比如我們每天用Sqoop抽取最新的一份全量數(shù)據(jù)到Hive中。
  • 方案二:每天保留一份全量的切片數(shù)據(jù)。
  • 方案三:使用拉鏈表。

如何設(shè)計拉鏈表

案例參考-漫談數(shù)據(jù)倉庫之拉鏈表(原理、設(shè)計以及在Hive中的實現(xiàn))
我們先看一下在Mysql關(guān)系型數(shù)據(jù)庫里的user表中信息變化。
在2017-01-01這一天表中的數(shù)據(jù)是:

注冊日期 用戶編號 手機號碼
2017-01-01 001 111111
2017-01-01 002 222222
2017-01-01 003 333333
2017-01-01 004 444444

在2017-01-02這一天表中的數(shù)據(jù)是, 用戶002和004資料進行了修改,005是新增用戶:

注冊日期 用戶編號 手機號碼 備注
2017-01-01 001 111111
2017-01-01 002 233333 由222222變成2333333
2017-01-01 003 333333
2017-01-01 004 43434343 由444444變成43434343
2017-01-02 005 555555 2017-01-02新增

在2017-01-03這一天表中的數(shù)據(jù)是, 用戶004和005資料進行了修改,006是新增用戶:

注冊日期 用戶編號 手機號碼 備注
2017-01-01 001 111111
2017-01-01 002 233333 由222222變成2333333
2017-01-01 003 333333
2017-01-01 004 654321 由43434343變成654321
2017-01-02 005 511111 由555555變成511111
2017-01-03 006 666666 2017-01-03新增

如果在數(shù)據(jù)倉庫中設(shè)計成歷史拉鏈表保存該表,則會有下面這樣一張表,這是最新一天(即2017-01-03)的數(shù)據(jù):

拉鏈表設(shè)計.PNG

說明:

  • t_start_date表示該條記錄的生命周期開始時間,t_end_date表示該條記錄的生命周期結(jié)束時間。
  • t_end_date = '9999-12-31'表示該條記錄目前處于有效狀態(tài)。
  • 如果查詢當(dāng)前所有有效的記錄,則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實現(xiàn)拉鏈表

只考慮實現(xiàn),不考慮性能
-- 時間粒度:天 day,建模之前需要按照Kimball思想"四步走"戰(zhàn)略
以訂單表為例,數(shù)據(jù)如下,每天的訂單明細:

orderid createtime modifiedtime status
1   2016-08-20  2016-08-20  創(chuàng)建
2   2016-08-20  2016-08-20  創(chuàng)建
3   2016-08-20  2016-08-20  創(chuàng)建
1   2016-08-20  2016-08-21  支付
2   2016-08-20  2016-08-21  完成
4   2016-08-21  2016-08-21  創(chuàng)建
1   2016-08-20  2016-08-22  完成
3   2016-08-20  2016-08-22  支付
4   2016-08-21  2016-08-22  支付
5   2016-08-22  2016-08-22  創(chuàng)建

根據(jù)拉鏈表,我們先想得到

1  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  2016-08-20
1  2016-08-20  2016-08-21  支付 2016-08-21  2016-08-21
1  2016-08-20  2016-08-22  完成 2016-08-22  9999-12-31
2  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  2016-08-20
2  2016-08-20  2016-08-21  完成 2016-08-21  9999-12-31
3  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  2016-08-21
3  2016-08-20  2016-08-22  支付 2016-08-22  9999-12-31
4  2016-08-21  2016-08-21  創(chuàng)建 2016-08-21  2016-08-21
4  2016-08-21  2016-08-22  支付 2016-08-22  9999-12-31
5  2016-08-22  2016-08-22  創(chuàng)建 2016-08-22  9999-12-31

可以看出 1,2,3,4每個訂單的狀態(tài)都有,并且也能統(tǒng)計到當(dāng)前的有效狀態(tài)。

  • 初始化hive表
-- 創(chuàng)建訂單表,使用sqoop進行從mysql中導(dǎo)入hive
CREATE TABLE orders (
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING
) row format delimited fields terminated by '\t'
 
 -- 訂單全量,按天進行分區(qū)
CREATE TABLE ods_orders_inc (
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING
) PARTITIONED BY (day STRING)
row format delimited fields terminated by '\t'
 
 -- 訂單拉鏈表設(shè)計
CREATE TABLE dw_orders_his (
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING,
dw_start_date STRING,
dw_end_date STRING
) row format delimited fields terminated by '\t' ;

首先全量更新,我們先到2016-08-20為止的數(shù)據(jù)。
初始化,先把2016-08-20的數(shù)據(jù)初始化進去。

INSERT overwrite TABLE ods_orders_inc PARTITION (day = '2016-08-20')
SELECT orderid,createtime,modifiedtime,status
FROM orders
WHERE createtime < '2016-08-21' and modifiedtime <'2016-08-21';

刷新到dw中;

INSERT overwrite TABLE dw_orders_his
SELECT orderid,createtime,modifiedtime,status,
createtime AS dw_start_date,
'9999-12-31' AS dw_end_date
FROM ods_orders_inc
WHERE day = '2016-08-20';

結(jié)果測試如下:

select * from dw_orders_his;
OK
1  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  9999-12-31
2  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  9999-12-31
3  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  9999-12-31

生于需要增量更新:

INSERT overwrite TABLE ods_orders_inc PARTITION (day = '2016-08-21')
SELECT orderid,createtime,modifiedtime,status
FROM orders
WHERE (createtime = '2016-08-21'  and modifiedtime = '2016-08-21') OR modifiedtime = '2016-08-21';
 
select * from ods_orders_inc where day='2016-08-21';
OK
1  2016-08-20  2016-08-21  支付 2016-08-21
2  2016-08-20  2016-08-21  完成 2016-08-21
4  2016-08-21  2016-08-21  創(chuàng)建 2016-08-21

先放到增量表中,然后進行關(guān)聯(lián)到一張臨時表中,在插入到新表中,(同樣,可以采用其他語句進行實現(xiàn)):

DROP TABLE IF EXISTS dw_orders_his_tmp;
-- 創(chuàng)建表
CREATE TABLE dw_orders_his_tmp AS
SELECT orderid,
createtime,
modifiedtime,
status,
dw_start_date,
dw_end_date
FROM (
    SELECT a.orderid,
    a.createtime,
    a.modifiedtime,
    a.status,
    a.dw_start_date,
    CASE WHEN b.orderid IS NOT NULL AND a.dw_end_date > '2016-08-21' THEN '2016-08-21' ELSE a.dw_end_date END AS dw_end_date
    FROM dw_orders_his a
    left outer join (SELECT * FROM ods_orders_inc WHERE day = '2016-08-21') b
    ON (a.orderid = b.orderid)
    UNION ALL
    SELECT orderid,
    createtime,
    modifiedtime,
    status,
    modifiedtime AS dw_start_date,
    '9999-12-31' AS dw_end_date
    FROM ods_orders_inc
    WHERE day = '2016-08-21'
) x
ORDER BY orderid,dw_start_date;
 -- 將數(shù)據(jù)插入到拉鏈表中
INSERT overwrite TABLE dw_orders_his
SELECT * FROM dw_orders_his_tmp;

在根據(jù)上面步驟把2016-08-22號的數(shù)據(jù)更新進去,最后結(jié)果如下:

select * from dw_orders_his;
OK
1  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  2016-08-20
1  2016-08-20  2016-08-21  支付 2016-08-21  2016-08-21
1  2016-08-20  2016-08-22  完成 2016-08-22  9999-12-31
2  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  2016-08-20
2  2016-08-20  2016-08-21  完成 2016-08-21  9999-12-31
3  2016-08-20  2016-08-20  創(chuàng)建 2016-08-20  2016-08-21
3  2016-08-20  2016-08-22  支付 2016-08-22  9999-12-31
4  2016-08-21  2016-08-21  創(chuàng)建 2016-08-21  2016-08-21
4  2016-08-21  2016-08-22  支付 2016-08-22  9999-12-31
5  2016-08-22  2016-08-22  創(chuàng)建 2016-08-22  9999-12-31

至此,得到了我們想要的數(shù)據(jù)。

拉鏈表延申補充

流水表與拉鏈表的區(qū)別

流水表存放的是一個用戶的變更記錄,比如在一張流水表中,一天的數(shù)據(jù)中,會存放一個用戶的每條修改記錄,但是在拉鏈表中只有一個記錄。
這是拉鏈表設(shè)計時需要注意的一個粒度問題。我們當(dāng)然可以設(shè)置的粒度更小一些,一般按天足夠了。

查詢性能

拉鏈表當(dāng)然會遇到查詢性能的問題。比如我們存放了5年的拉鏈數(shù)據(jù),那么這張表勢必會比較大,當(dāng)查詢的時候性能就比較低了,個人認為有兩種解決思路:

  • 1.在一些查詢引擎中,我們對start_date和end_date做索引,這樣能提升不少性能。這種方法其實在hive中行不通,因為hive相當(dāng)于沒有索引,不過在其他系統(tǒng)中可以考慮。
  • 2.保留部分歷史數(shù)據(jù),比如說我們一張表里面存放全量的拉鏈表數(shù)據(jù),然后再對外暴露一張只提供近3個月數(shù)據(jù)的拉鏈表。

淘汰機制

關(guān)于淘汰機制,其實和性能也是有關(guān)系的,一方面是因為所有數(shù)據(jù)的積累會導(dǎo)致計算越來越慢,另一方面是業(yè)務(wù)側(cè)其實對歷史數(shù)據(jù)的需求也有一定的優(yōu)先級的。
因此再設(shè)計拉鏈表的時候可以制定一些數(shù)據(jù)的淘汰機制。淘汰的數(shù)據(jù)不一定要刪除,比如我們建立兩張拉鏈表,一張拉鏈表中只保存最新的十條數(shù)據(jù),其他的數(shù)據(jù)會保存入一張歷史拉鏈表中。

其他心得

1.使用拉鏈表的時候可以不加t_end_date,即失效日期,但是加上之后,能優(yōu)化很多查詢。
2.可以加上當(dāng)前行狀態(tài)標(biāo)識,能快速定位到當(dāng)前狀態(tài),
3.在拉鏈表的設(shè)計中可以加一些內(nèi)容,因為我們每天保存一個狀態(tài),如果我們在這個狀態(tài)里面加一個字段,比如當(dāng)天修改次數(shù),那么拉鏈表的作用會恒大。

參考博客:
http://www.itdecent.cn/p/799252156379 漫談數(shù)據(jù)倉庫之拉鏈表(原理、設(shè)計以及在Hive中的實現(xiàn))推薦大家好好的讀一下木東居士的文章
http://www.cnblogs.com/wujin/p/6121754.html hive中拉鏈表
http://lxw1234.com/archives/2015/08/473.htm 數(shù)據(jù)倉庫中歷史拉鏈表的更新方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容