拉链表是数据仓库中处理缓慢变化维度(SCD Type 2) 的核心设计,通过start_date/end_date标记每条记录的生命周期,只存变化、不存冗余,兼顾历史追溯与存储效率。
SCD = Slowly Changing Dimension 慢速变化维度
++PS.SCD介绍开始。++
一、SCD 是什么
SCD = Slowly Changing Dimension 慢速变化维度 指:维度表字段不会频繁变、偶尔变(商品价格 / 分类 / 品牌、用户等级、客户地区、员工部门),需要保留历史 or 覆盖更新。
二、三大核心类型(工作必懂)
1)SCD Type 1 直接覆盖(不留历史)
- 逻辑:新值直接覆盖旧值,无任何历史记录
- 场景:不需要追溯、只看最新
- 例子:商品临时备注、无关紧要修正
- 缺点:查不到昨天 / 上个月的值
plaintext
原:价格99
改:直接更新为129 → 旧99彻底消失
2)SCD Type 2 保留全历史(你现在的拉链表就是它❗)
- 逻辑:不改旧记录,关闭旧生命周期 + 新增一条新记录
- 靠:
start_date / end_date / is_current实现 - 场景:商品价格、上下架、分类迁移、会员等级、绩效维度
- 优点:任意时间点快照、审计、复盘、报表回算都支持
你刚才的 MySQL 商品拉链表 = 标准 SCD2 工业实现
plaintext
旧:99 [2026-01-01, 2026-04-01) 失效
新:129 [2026-04-01, 9999-12-31) 当前
3)SCD Type 3 保留最近 1~2 次历史(加字段)
- 逻辑:加扩展字段存旧值
- 例如:
old_price / new_price、prev_category - 场景:只需要看上一次变更,不需要完整轨迹
- 缺点:历史一多就废,存不了多次变化
三、SCD 类型对比(一眼记牢)
表格
| 类型 | 别名 | 历史保留 | 实现方式 | 适合场景 |
|---|---|---|---|---|
| Type1 | 覆盖更新 | ❌ 无历史 | update 直接改 | 简单修正、不重要字段 |
| Type2 | 拉链 / 历史版 | ✅ 全历史 | 起止时间多条记录 | 商品、用户、员工、核心维度(你在用) |
| Type3 | 新增旧字段 | ⭕ 最近 1 次 | 加 old_xxx 字段 | 只对比上一次变化 |
四、SCD2 常用两种落地方式
- 拉链表(你现在 MySQL 这套) 日期区间
[start,end),一条一条链起来,最通用、省存储。 - 版本号 SCD2 加
version,每次变更 version+1,搭配生效时间。
五、结合你的「商品业务」怎么选
- 商品名称 / 分类 / 品牌 / 售价 / 上下架:必须 SCD2(拉链)
- 商品备注、临时标签纠错:可用 SCD1
- 只需要对比上次调价:局部字段用 SCD3
六、快速一句话总结
- SCD1:盖掉过去
- SCD2:记住所有(拉链表就是它)
- SCD3:只记上一次
++PS.SCD介绍截止++
一、核心定义与原理
- 定义 :为每条数据记录添加生效时间(start_date)与失效时间(end_date) ,数据变更时不修改原记录 ,而是关闭旧记录(更新 end_date)+ 插入新记录,形成完整历史链。
- 核心思想 :每条记录代表业务实体在 [start_date, end_date) 时间段内的唯一状态;end_date='9999-12-31' 表示当前有效记录。
- 对比快照表:快照表每日全量复制,空间浪费大;拉链表仅存变更,空间节省 90%+,且支持任意时间点回溯。
二、适用场景
- 维度属性频繁但非每日变化(如用户等级、商品价格、员工部门)。
- 需要查询历史快照(如 "2026-01-01 该用户的会员等级")。
- 数据量大、全量快照成本高,需平衡存储与查询。
- 不适合:数据每日全量变化、仅需最新状态、强事务一致性要求(如交易流水)。
三、表结构设计(核心)
1. 必选字段
表格
| 字段 | 类型 | 说明 |
|---|---|---|
| 业务主键 | 如 user_id、sku_id | 唯一标识业务实体 |
| 属性字段 | 如 name、level、price | 业务维度属性 |
| start_date | DATE | 记录生效日期(含) |
| end_date | DATE | 记录失效日期(不含),当前有效为9999-12-31 |
2. 可选优化字段
- is_current:BOOLEAN,标记是否当前有效(1/0),简化查询。
- dw_create_dt:数据入仓时间,用于数据血缘与审计。
- dw_update_dt:记录更新时间,用于增量同步。
3. 主键与索引
- 逻辑主键:业务主键 + start_date(确保同一实体不同版本唯一)。
- 物理主键 :建议加代理主键(如 id),提升关联查询性能。
- 索引:对(业务主键,start_date, end_date)建复合索引;Hive 可按 start_date 分区。
4. 示例表(用户会员等级拉链表)
CREATE TABLE user_level_chain (
id BIGINT COMMENT '代理主键',
user_id STRING COMMENT '用户ID',
level STRING COMMENT '会员等级',
start_date DATE COMMENT '生效日期',
end_date DATE COMMENT '失效日期',
is_current BOOLEAN COMMENT '是否当前有效',
dw_create_dt DATE COMMENT '入仓时间'
)
STORED AS ORC;
四、数据处理流程(核心实现)
1. 初始化(首次加载)
-
全量导入当前有效数据,start_date = 首次生效日 ,end_date='9999-12-31' ,is_current=true。
INSERT INTO user_level_chain
SELECT
ROW_NUMBER() OVER(), user_id, level, '2026-01-01', '9999-12-31', true, CURRENT_DATE
FROM user_level_current;
2. 日常增量更新(核心步骤)
假设每日 T 日处理 T-1 日增量:
- 提取增量:获取 T-1 日发生变更的用户数据(新增 / 修改)。
- 关闭旧记录 :将拉链表中对应用户的当前有效记录(end_date='9999-12-31')的end_date 更新为 T-1 日 ,is_current 设为 false。
- 插入新记录 :为变更用户插入新记录,start_date=T 日 ,end_date='9999-12-31' ,is_current=true。
核心 SQL(Hive 示例)
-- 步骤1:临时表存T-1日增量
CREATE TABLE user_level_delta AS
SELECT user_id, level FROM user_level_source WHERE dt = '2026-03-31';
-- 步骤2:合并拉链(INSERT OVERWRITE)
INSERT OVERWRITE TABLE user_level_chain
-- 1. 未变化的当前有效记录(保留)
SELECT id, user_id, level, start_date, end_date, is_current, dw_create_dt
FROM user_level_chain
WHERE end_date = '9999-12-31'
AND user_id NOT IN (SELECT user_id FROM user_level_delta)
UNION ALL
-- 2. 旧记录失效(end_date设为T-1)
SELECT id, user_id, level, start_date, '2026-03-31' AS end_date, false AS is_current, dw_create_dt
FROM user_level_chain
WHERE end_date = '9999-12-31'
AND user_id IN (SELECT user_id FROM user_level_delta)
UNION ALL
-- 3. 插入新记录(start_date=T日)
SELECT
ROW_NUMBER() OVER() + (SELECT MAX(id) FROM user_level_chain),
user_id, level, '2026-04-01' AS start_date, '9999-12-31' AS end_date, true AS is_current, CURRENT_DATE
FROM user_level_delta;
五、查询示例
-
查询当前最新状态
SELECT * FROM user_level_chain WHERE is_current = true;
-- 或 WHERE end_date = '9999-12-31'; -
查询历史快照(2026-02-01)
SELECT * FROM user_level_chain
WHERE start_date <= '2026-02-01' AND end_date > '2026-02-01'; -
查询用户历史变更链
SELECT * FROM user_level_chain WHERE user_id = 'U001' ORDER BY start_date;
六、优缺点
优点
- 省空间:仅存变更,无冗余,适合大数据量。
- 全历史:支持任意时间点回溯,满足分析需求。
- 易维护:逻辑清晰,增量处理简单。
缺点
- 写入复杂:需关闭旧记录 + 插入新记录,事务性要求高(Hive 需开启事务)。
- 查询稍慢:需时间范围过滤,不如单条最新记录快(可建索引 / 分区优化)。
- 数据膨胀:长期运行记录数会持续增长,需定期归档冷数据。
七、设计要点与最佳实践
- 时间粒度 :按天处理最常见;业务允许可按小时 / 分钟,但复杂度上升。
- end_date 规则 :统一用
9999-12-31表示当前有效,避免空值问题。 - 事务支持 :Hive 需开启
hive.support.concurrency=true与事务管理器,确保更新原子性。 - 增量识别 :源表需有modify_time/version标记,避免漏更 / 重复更。
- 数据归档:对 end_date < 3 年前的记录归档,提升查询性能。
- 避免删除 :拉链表只做插入 + 更新 end_date,不物理删除,保证历史完整。
八、与其他 SCD 方案对比
表格
| 方案 | 存储 | 历史 | 复杂度 | 适用 |
|---|---|---|---|---|
| 拉链表(SCD2) | 中(仅存变更) | 完整 | 中 | 需历史、数据量大 |
| 全量快照 | 高(每日全量) | 完整 | 低 | 数据量小、每日全量 |
| 保留最新(SCD1) | 低(仅最新) | 无 | 低 | 无需历史 |
| 新增列(SCD3) | 中(有限历史) | 有限 | 中 | 仅需最近几次变化 |