場景
有一張明細事務級別的流水表,主鍵是事件流水號srl_id, 該表每天采集當天新增及變化的事件下發,上游下發文件分區日期prt_dt.
存在這樣的情況,某個流水號srl_id在20210101發生,會在prt_dt=20200101的分區首次下發,若之后在20200105發生改變,在prt_dt=20200105會再次下發。
每個流水號都有一個estb_dt,即首次發生日期,同一srl_id,該日期值不變。
需求是:下游每天接收處理數據,對在20200105發生改變的srl_id,要在下游應用保留分區20200101中更新,即讓這個發生改變的流水號srl_id信息在下游第一次落地的分區日期始終保持最新的狀態,這個下游分區其實等價於estb_dt首次發生日期。
分析
一般的寫法往往有全表掃描:
insert overwrite table full_data_table -- 全量 select a.pk_col a.data_col from full_data_table a left join inc_data_table b --數據量很大,會造成全局掃描 on a.pk_col = b.pk_col and b.date=execution_date where a.date<execution_date and b.pk_col is null --沒受影響的數據 union all select cc.data_col from inc_data_table cc where date=execution_date;
對歷史分區的有更改數據做UPDATE操作;難點在於如何避免全表掃描找有更改的srl_id
優化方法
1. tb_a下游累數分區全量表,選擇相對業務srl_id不變的時間字段estb_dt做分區字段,每個分區存放當天estb_dt的srl_id數據;
2. 當天下發的新數據,包含新增業務數據+新增更改數據,建臨時增量表 tb_b,也以estb_dt做分區(這里是否有必要?沒必要,這里左表大數據量的關聯鍵一定要是分區鍵,不能全表掃描,但b表只要數據量足夠小默認放進內存就行,如果b表沒法小怎么辦?)
3.通過left semi join 定位出增量表影響到的分區,關聯字段取分區字段,避免全表掃描(bug: 1.這里增量臨時分區表是否必要,下發增量數據不以estb_dt分區,而是以prt_dt分區會避免全表掃描嗎?可的,其實主要是避免a表全表掃描,b表起過濾作用,足夠小可以放進內存就可以)
4.通過left join去除受影響分區中要發生變化的數據,這部分即無變化的歷史數據
5.再union all當天增量下發數據:即新增業務數據+變更業務數據,就是目標數據
6.這里腳本都未加時間限制,加了時間限制后支持歷史執行日期重新調度
一些小疑問:這里若A是增量表,B是全量表也可以操作,只是left semi join會變成左表小,右表大,這樣性能會更好嗎?
-- 創建增量數據臨時表 create table if not exist tb_b ...partition by(esdb_dt int '首次業務日期') insert overwrite tb_b partition(esdb_dt) select ... --當天數據加工 insert overwrite tb_a partition(estb_dt) select a.srl_id a.estb_dt from tb_a a -- 全量 left semi join tb_b b1 -- 增量,用left join on key防止全表掃描 on a.estb_dt=b1.estb_dt -- 獲取歷史表受影響的數據分區 left join tb_b b -- 增量 on a.srl_id = b.srl_id where b.srl_id is null -- 過濾掉b表出現變更的記錄,得到未變更歷史 union all select --加上增量表(新增srl_id+變更srl_id) b.srl_id b.estb_dt from tb_b b -- 增量
一些其他帖子
full join :https://blog.csdn.net/magicharvey/article/details/20692829
https://my.oschina.net/sniperLi/blog/755273
每次只更新發生變化的分區:https://blog.csdn.net/wujiandao/article/details/80413661
rownumber:http://www.fengxiaokai.cn/archives/27