4. 缓慢变化维度(SCD)陷阱:错误选择SCD策略,导致历史数据丢失或存储爆炸
SCD 策略选错了,后果很直接——要么历史数据丢了,要么存储炸了。我见过太多团队在这上面栽跟头。
说白了,SCD 就是处理维度属性变化的一套规则。比如客户的地址变了、产品的分类调整了,你怎么在数据仓库里记录这个变化?
选错了策略,轻则分析结果对不上,重则整个数仓重做。嗯,这里咱们得好好捋一捋。
4.1 三种经典 SCD 策略,你真的用对了吗?
先快速过一遍基础。SCD 有三大经典策略,我直接给你列个表对比:
| 策略 | 核心逻辑 | 存储方式 | 典型场景 |
|---|---|---|---|
| SCD 类型 1 | 直接覆盖旧值 | 只保留最新值 | 错误修正、不重要属性 |
| SCD 类型 2 | 新增一条记录 | 保留全部历史 | 客户地址、产品分类 |
| SCD 类型 3 | 增加新列存储旧值 | 只保留有限历史 | 有限回溯分析 |
看起来很简单对吧?但陷阱就藏在细节里。
4.2 陷阱一:滥用 SCD 类型 1,历史数据说丢就丢
我遇到过一家电商公司,他们的客户维度表一直用类型 1。客户改个手机号,直接覆盖。结果财务部门要做历史订单的客户联系方式回溯,发现所有老订单的客户手机号都是最新的——完全对不上账。
为什么会这样? 因为类型 1 的设计初衷就是「不保留历史」。你覆盖了,就再也找不回来了。
避坑指南: 我曾经在金融项目中吃过这个亏。后来定了个规矩:凡是涉及审计、合规、财务对账的维度属性,一律不用类型 1。哪怕业务方说「这个属性不重要」,你也要多问一句:「三个月后要查历史数据怎么办?」
什么时候可以用类型 1?
- 数据录入错误修正(比如把「张三」改成「张三」)
- 不参与历史分析的属性(比如客户昵称)
- 业务上明确不需要回溯的属性
4.3 陷阱二:无脑使用 SCD 类型 2,存储爆炸
类型 2 是保留历史最彻底的方式,但也是最容易「存储爆炸」的。
我见过一个极端案例:某零售企业的产品维度表,每天跑全量更新,所有属性都按类型 2 处理。结果呢?一张产品维度表,半年时间从 10 万条膨胀到 800 万条。查询性能直线下降,ETL 跑一次要 6 小时。
问题出在哪? 他们把「产品上架时间」「产品状态」这种高频变化的属性也用了类型 2。你想想看,产品状态每天变几次,每次变都新增一条记录,不爆炸才怪。
我的建议: 类型 2 只用于「真正需要保留历史」且「变化频率可接受」的属性。对于高频变化属性,要么拆成事实表,要么用类型 1 覆盖。
类型 2 的正确打开方式:
- 给每条记录加上
valid_from和valid_to时间戳 - 加上
current_flag标记当前有效记录 - 定期归档过期记录到历史表
-- 类型 2 维度表示例
CREATE TABLE dim_customer (
customer_sk INT PRIMARY KEY, -- 代理键
customer_id INT, -- 业务主键
customer_name VARCHAR(100),
address VARCHAR(200),
valid_from DATE, -- 生效日期
valid_to DATE, -- 失效日期(NULL 表示当前有效)
current_flag CHAR(1) DEFAULT 'Y' -- Y/N
);
4.4 陷阱三:类型 3 的「有限历史」陷阱
类型 3 看起来很美——加一列存旧值,既能回溯又能节省空间。但它的局限性很明显:只能保留一次变化。
我有个客户,用类型 3 记录客户的「省」属性变化。第一次变化没问题,第二次变化时,旧值就被新值覆盖了。结果业务方要查「客户最初在哪个省」,数据已经没了。
类型 3 适合什么场景?
- 只需要对比「当前值」和「上一次值」
- 变化次数极少(比如一年变一次)
- 存储空间极度受限
核心原则: 类型 3 不是类型 2 的替代品,它是特定场景下的折中方案。别指望用它来保留完整历史。
4.5 如何正确选择 SCD 策略?
我个人习惯用这个决策流程,你可以参考一下:
- 这个属性需要保留历史吗? 不需要 → 类型 1
- 需要保留完整历史吗? 需要 → 类型 2
- 只需要对比当前和上一次? 是 → 类型 3
- 变化频率高且需要历史? 考虑拆成事实表
下面这张图把决策逻辑画清楚了:
4.6 混合策略:现实世界的最优解
实际项目中,我很少只用一种策略。大多数时候是混合使用。
举个例子,客户维度表:
- 客户姓名:类型 1(错误修正用覆盖)
- 客户地址:类型 2(需要完整历史)
- 客户等级:类型 3(只需要对比当前和上一次)
- 最后登录时间:类型 1(高频变化,不保留历史)
我的经验: 设计维度表时,先和业务方确认每个属性的「历史回溯需求」。别自己猜,猜错了就是坑。我曾经因为没问清楚,把「客户等级」用了类型 2,结果一年后存储翻了 10 倍,业务方却说「我们只看当前等级」——白费功夫。
4.7 存储爆炸的终极解法:归档与分区
即使选对了策略,类型 2 的维度表还是会越来越大。怎么办?
两个手段:
- 分区表:按时间分区,过期数据自动归档到冷存储
- 历史表分离:当前有效记录放主表,过期记录移入历史表
-- 分区表示例(按 valid_from 月份分区)
CREATE TABLE dim_customer_partitioned (
customer_sk INT,
customer_id INT,
address VARCHAR(200),
valid_from DATE,
valid_to DATE,
current_flag CHAR(1)
)
PARTITION BY RANGE (valid_from) (
PARTITION p202301 VALUES LESS THAN ('2023-02-01'),
PARTITION p202302 VALUES LESS THAN ('2023-03-01'),
PARTITION p_future VALUES LESS THAN (MAXVALUE)
);
嗯,SCD 策略没有银弹。选错了,要么丢历史,要么炸存储。我的建议是:先搞清楚业务需求,再选策略。别一上来就类型 2 走天下,也别图省事全用类型 1。
记住一句话:维度建模的灵活性,来自于对业务变化的深刻理解,而不是对某种策略的盲目崇拜。