在数据仓库建设中,全量表、增量表、拉链表是三种核心的数据更新策略,用于处理源系统数据变化(如新增、修改、删除)在数仓中的落地方式。它们各有适用场景,设计目标是在数据准确性、存储成本、查询效率之间取得平衡。
下面从定义、适用场景、设计方式、优缺点四个维度详细讲解:
一、全量表(Full Load)
✅ 定义
每天(或每次同步)完整覆盖目标表,保留最新状态的全量快照。
📌 适用场景
- 源表数据量小(如维度表:用户表、商品表 < 100万行)
- 数据变化频繁,但只需保留当前最新状态
- 不关心历史变化过程
🔧 设计方式
- ODS层:每日分区存储原始全量快照(如
ods_user_df/dt=20241105) - DWD/DWS层:每日用最新分区覆盖写入一张“当前全量表”
-- 示例:每日重建当前用户全量表
INSERT OVERWRITE TABLE dwd_user_full
SELECT
user_id,
user_name,
gender,
city,
update_time
FROM ods_user_df
WHERE dt = '20241105'; -- 取最新一天的全量
✅ 优点
- 实现简单,逻辑清晰
- 查询最新状态极快(直接查一张表)
❌ 缺点
- 无法追溯历史:昨天用户在哪个城市?不知道。
- 存储浪费:即使只变1条,也存全部数据(但可通过分区管理缓解)
二、增量表(Incremental Load)
✅ 定义
只同步新增或变化的数据(通常通过 create_time 或 update_time 判断),以追加(Append) 方式写入。
📌 适用场景
- 事实表(如订单、日志):天然具有“新增”属性,极少更新
- 源系统能提供明确的增量标识(如自增ID、时间戳)
- 不需要更新历史记录(或历史不可变)
🔧 设计方式
- ODS层:按天分区,只接入当日新增数据(如
ods_order_inc/dt=20241105) - DWD层:直接按分区插入,不做去重或更新
-- 示例:订单事实表(只追加)
INSERT INTO dwd_order_inc PARTITION (dt='20241105')
SELECT
order_id,
user_id,
amount,
create_time
FROM ods_order_inc
WHERE dt = '20241105';
⚠️ 注意:如果源系统存在延迟数据(如昨天的订单今天才上报),需设计重跑机制或使用拉链表/合并更新。
✅ 优点
- 存储高效:只存变化部分
- 写入性能高:无需读取历史数据
❌ 缺点
- 无法处理更新:若订单状态从“已支付”变为“已退款”,增量表无法体现
- 数据不完整:单日分区不能代表全貌,需跨分区聚合
三、拉链表(Slowly Changing Dimension Type 2, SCD-2)
✅ 定义
通过生效时间区间(start_date, end_date)记录维度属性的完整历史变化轨迹,是处理缓慢变化维度的标准方案。
📌 适用场景
- 维度表中关键属性会变化,且业务需要追溯历史
(如:用户等级、商品类目、员工部门) - 需要回答“某时间点,某实体是什么状态?”
(如:“2024年6月1日,用户A是什么会员等级?”)
🔧 设计方式(核心字段)
| 字段名 | 说明 |
|---|---|
xxx_sk | 代理键(Surrogate Key),唯一标识每条历史记录 |
natural_key | 业务主键(如 user_id) |
attr1, attr2... | 维度属性(如 level, city) |
start_date | 该版本生效开始日期 |
end_date | 该版本生效结束日期(9999-12-31 表示当前最新) |
is_current | 是否为当前最新版本(可选) |
🔄 更新逻辑(每日处理流程)
- 读取昨日拉链表(历史全量)
- 读取今日ODS增量变化数据
- 关联比对:找出哪些业务主键的属性发生了变化
- 更新旧记录:将发生变化的旧记录
end_date改为今天 - 插入新记录:新增一条
start_date=今天, end_date=9999-12-31的记录
💡 SQL 伪代码示例(Hive/Spark SQL):
-- 1. 今日变化数据(来自ODS)
WITH today_changed AS (
SELECT user_id, level, city
FROM ods_user_df
WHERE dt = '20241105'
),
-- 2. 昨日拉链表(当前最新版本)
yesterday_latest AS (
SELECT *
FROM dim_user_zip
WHERE end_date = '9999-12-31'
),
-- 3. 找出变化的用户
changed_users AS (
SELECT y.user_id
FROM yesterday_latest y
JOIN today_changed t ON y.user_id = t.user_id
WHERE y.level != t.level OR y.city != t.city
)
-- 4. 重新构建拉链表
INSERT OVERWRITE TABLE dim_user_zip
-- 未变化的记录:保留原样
SELECT user_sk, user_id, level, city, start_date, end_date
FROM yesterday_latest
WHERE user_id NOT IN (SELECT user_id FROM changed_users)
UNION ALL
-- 变化的旧记录:关闭有效期
SELECT user_sk, user_id, level, city, start_date, '2024-11-04' AS end_date
FROM yesterday_latest
WHERE user_id IN (SELECT user_id FROM changed_users)
UNION ALL
-- 新增的当前记录
SELECT
next_user_sk() AS user_sk, -- 生成新代理键
user_id,
level,
city,
'2024-11-05' AS start_date,
'9999-12-31' AS end_date
FROM today_changed
WHERE user_id IN (SELECT user_id FROM changed_users);
✅ 优点
- 完整保留历史:可精确回溯任意时间点的状态
- 查询灵活:支持“某时间点快照”、“历史变化趋势”分析
❌ 缺点
- 实现复杂:ETL逻辑较重,需处理代理键、时间区间
- 存储膨胀:每变一次就新增一行(但通常维度变化不频繁)
- 查询稍复杂:需加时间区间过滤
四、对比总结
| 特性 | 全量表 | 增量表 | 拉链表 |
|---|---|---|---|
| 数据内容 | 当前最新全量快照 | 仅新增/变化数据 | 完整历史版本 |
| 是否保留历史 | ❌ 否 | ❌ 否(仅新增) | ✅ 是 |
| 适用表类型 | 小型维度表 | 事实表、日志表 | 缓慢变化维度表 |
| 存储成本 | 中(每日全量) | 低(只存增量) | 高(每变一行) |
| 查询效率 | ⭐⭐⭐⭐⭐(查最新) | ⭐⭐(需跨分区) | ⭐⭐⭐(需时间过滤) |
| 实现复杂度 | 低 | 低 | 高 |
| 典型场景 | 当前用户列表 | 订单流水、点击日志 | 用户等级、商品类目变更 |
五、如何选择?
- 事实表 → 优先用 增量表(天然不可变)
- 小维度表 + 无需历史 → 用 全量表
- 关键维度 + 需要历史追溯 → 必须用 拉链表(SCD-2)
💡 高级技巧:
对于超大维度表(如用户表上亿),可采用 “全量快照 + 拉链变化日志” 混合模式:
- 每月做一次全量快照
- 日级别用拉链记录变化
既控制存储,又支持历史查询。
✅ 总结一句话:
全量看现在,增量记流水,拉链留历史。
根据业务是否需要“回到过去”,决定是否上拉链表。
5928

被折叠的 条评论
为什么被折叠?



