维度建模基础:星型模型与雪花模型、事实表与维度表、缓慢变化维
聊到数据仓库建模,维度建模是绕不开的核心。我刚开始做数仓那会儿,觉得不就是画几张表嘛,后来踩了坑才明白——模型设计的好坏,直接决定了后续ETL好不好写、查询快不快、业务方骂不骂娘。
这一章,咱们把维度建模的底子打扎实。说白了,就是搞清楚三件事:星型和雪花怎么选、事实表和维度表怎么分、缓慢变化维怎么处理。
星型模型 vs 雪花模型
先看两张最经典的模型图。我习惯把星型模型叫做“一张大桌子围着一圈小凳子”——中间是事实表,周围是维度表,直接关联,不绕弯子。
核心区别一句话:星型模型是维度表直接连事实表,雪花模型是维度表再连子维度表。
我在项目中遇到过不少团队,一上来就选雪花模型,理由是“数据更规范”。结果呢?查询慢了3倍,业务方天天催。你想想看,数仓的核心目标是快速响应分析,不是给数据库做范式设计。
| 对比维度 | 星型模型 | 雪花模型 |
|---|---|---|
| 查询性能 | 高(JOIN少) | 低(JOIN多) |
| 存储空间 | 较大(冗余) | 较小(规范化) |
| ETL复杂度 | 简单 | 较复杂 |
| 业务理解 | 直观 | 需要熟悉表关系 |
| 适用场景 | OLAP、报表、BI | 数据治理要求高的场景 |
我的建议:80%的场景用星型模型就够了。除非你的维度表有上百个字段、且存储成本敏感,才考虑雪花化。
事实表与维度表
这两个概念是维度建模的基石。我习惯这么记:事实表记录“发生了什么”,维度表描述“是谁、在哪、什么时候”。
事实表
事实表的核心是度量值——销售额、订单量、点击次数,这些数字是可以加总、求平均的。事实表一般很大,动辄几亿行。
举个例子,电商订单事实表:
CREATE TABLE fact_order (
order_id BIGINT,
date_sk INT, -- 时间维度外键
product_sk INT, -- 产品维度外键
store_sk INT, -- 门店维度外键
customer_sk INT, -- 客户维度外键
quantity INT, -- 数量(可加总)
amount DECIMAL, -- 金额(可加总)
discount DECIMAL -- 折扣(半可加)
);
注意看,事实表的外键指向维度表,度量字段才是分析的核心。我记得有一次,业务方要算“每个门店的月均客单价”,如果事实表设计得好,一条SQL就搞定。
维度表
维度表是“描述性”的,字段多是文本、分类、层级。比如产品维度:
CREATE TABLE dim_product (
product_sk INT PRIMARY KEY,
product_id VARCHAR(20),
product_name VARCHAR(100),
category VARCHAR(50),
brand VARCHAR(50),
price DECIMAL,
effective_date DATE,
expired_date DATE
);
维度表一般不会太大,几千到几百万行。但维度表的设计质量,直接决定了分析的灵活度。你想想看,如果产品维度里没有“品牌”字段,业务方想按品牌分析销售额,那就得重新ETL——这就是设计时的坑。
避坑指南:维度表不要只存ID,要把业务上常用的描述字段都加进去。我曾经见过一个客户维度表只有customer_id和name,结果业务方每次都要去CRM系统查地址、等级——这就是典型的维度表设计不足。
缓慢变化维(SCD Type 1/2/3)
现实世界的数据是会变的。客户的地址变了、产品的价格调了、员工的部门换了。怎么处理这些变化?这就是缓慢变化维要解决的问题。
我习惯把SCD分成三类,用一句话记住:Type 1覆盖、Type 2保留历史、Type 3记快照。
Type 1:直接覆盖
最简单粗暴。客户改了地址,直接把旧地址覆盖成新地址。优点是实现简单,缺点是历史没了。
-- 客户改了地址,直接UPDATE
UPDATE dim_customer
SET address = '新地址'
WHERE customer_sk = 123;
什么时候用?错误修正、不重要的属性。比如客户姓名打错了,直接改掉就行。
Type 2:保留历史
这是最常用的方式。每次变化都新增一条记录,用生效日期和失效日期来标记版本。
-- 客户改了地址,新增一条记录
INSERT INTO dim_customer (customer_sk, customer_id, address, effective_date, expired_date, is_current)
VALUES (456, 'C001', '新地址', '2024-06-01', '9999-12-31', 1);
-- 旧记录标记为失效
UPDATE dim_customer
SET expired_date = '2024-05-31', is_current = 0
WHERE customer_sk = 123;
Type 2的好处是能回溯历史——比如分析“去年每个地址的客户数”,Type 2就能做到。代价是维度表会膨胀,但现代数仓完全扛得住。
我的经验:金融、保险、审计类业务,必须用Type 2。我曾经帮一个银行做数仓,他们需要查三年前的客户地址——没有Type 2根本做不到。
Type 3:记录快照
Type 3是在维度表里加几个“历史字段”,比如“原地址”和“现地址”。只能保留最近一次变化。
-- 客户改了地址,保留原地址
UPDATE dim_customer
SET previous_address = address, -- 把当前地址移到历史字段
address = '新地址' -- 写入新地址
WHERE customer_sk = 123;
Type 3用得少,因为只能记一次变化。什么时候用?只需要对比“变化前后”的场景,比如分析“搬家后消费行为有没有变化”。
| SCD类型 | 实现方式 | 历史保留 | 存储开销 | 典型场景 |
|---|---|---|---|---|
| Type 1 | 直接覆盖 | 不保留 | 低 | 错误修正、不重要属性 |
| Type 2 | 新增版本行 | 完整保留 | 高 | 金融、审计、客户分析 |
| Type 3 | 增加历史字段 | 仅保留上一次 | 中 | 前后对比分析 |
选型建议:如果业务方说“我要看历史数据”,直接上Type 2。如果他们说“改了就改了,不用管旧的”,Type 1就够了。Type 3作为补充,别当主力。
嗯,维度建模的基础就这些。星型模型和雪花模型选型、事实表和维度表的分工、SCD的处理策略——这三块搞清楚了,数仓建模的骨架就有了。剩下的就是根据业务场景去填充血肉。