4. 数据建模与设计:维度建模与事实建模的选择、缓慢变化维(SCD)的处理策略、数据模型的可扩展性设计
数据建模这事儿,说白了就是给数据搭房子。房子搭得稳,后面住着才舒心。我做了这么多年数据产品,见过太多因为建模阶段偷懒,导致后面ETL跑不动、报表查不出来、业务方天天投诉的案例。
今天咱们就聊聊建模中最核心的三个问题:选哪种建模方式、怎么处理维度变化、以及如何让模型能扛住未来三到五年的业务扩展。
4.1 维度建模 vs 事实建模:到底该怎么选?
很多刚入行的同学会纠结这个问题。其实没那么复杂,你想想看,业务方问你的问题是什么类型的?
核心判断标准:如果业务方问的是「某个维度下,某个指标是多少」,那就用维度建模。如果问的是「某个事件发生后,接下来发生了什么」,那就考虑事实建模。
我个人的习惯是,90%的场景先用维度建模试水。为什么?因为维度建模对业务方最友好,星型结构一看就懂。
| 对比维度 | 维度建模 | 事实建模 |
|---|---|---|
| 核心结构 | 星型/雪花型 | 实体-关系(ER) |
| 适用场景 | OLAP、报表、BI分析 | OLTP、事务处理、流程追踪 |
| 查询效率 | 高(预聚合友好) | 中(需多表关联) |
| 扩展性 | 维度变化需SCD策略 | 新增实体较灵活 |
| 业务理解成本 | 低 | 高 |
我在项目中遇到过这样一个情况:某电商平台要做用户行为分析,产品经理一开始要求用事实建模,把每次点击、浏览、加购都做成事件实体。结果模型建完,业务方看不懂,每次查个转化率要关联七八张表。后来我建议改成维度建模,把用户、商品、时间作为维度,行为作为事实表,问题一下子就解决了。
我的建议:数据产品化初期,优先用维度建模。等业务稳定了,再考虑用事实建模补充流程分析能力。
4.2 缓慢变化维(SCD)的处理策略
维度不是一成不变的。用户会改地址,商品会调价格,部门会重组。这些变化怎么处理?这就是SCD要解决的问题。
嗯,这里要注意,SCD不是银弹,选错了策略,数据查起来会让人抓狂。
4.2.1 三种经典策略
- SCD Type 1(覆盖):直接更新,不保留历史。适合「错了就改」的场景,比如用户手机号录入错误。
- SCD Type 2(新增行):保留历史版本,新增一条记录。适合需要追溯历史的场景,比如用户等级变化。
- SCD Type 3(新增列):在原有行上增加列,记录变化前后的值。适合只关心最近一次变化的场景。
我曾经犯过一个错:对用户地址用了Type 2,结果一个用户搬家三次,维度表里就多了三条记录。业务方查「当前地址」时,还得加一堆过滤条件。后来我改成Type 1 + 快照表的方式,既保留了历史,又保证了查询效率。
避坑指南:Type 2虽然灵活,但会导致维度表膨胀。我建议给每个维度行加上valid_from和valid_to时间戳,配合is_current标志位,查询时直接过滤is_current = true。
4.2.2 实际场景中的选择矩阵
| 变化类型 | 推荐策略 | 原因 |
|---|---|---|
| 属性值纠错 | Type 1 | 历史错误无意义 |
| 自然缓慢变化(如地址) | Type 2 | 需要追溯历史 |
| 等级/状态变化 | Type 2 | 分析不同等级下的行为差异 |
| 仅需最新值 | Type 1 | 减少维度表复杂度 |
| 需要记录变化原因 | Type 2 + 原因列 | 便于审计 |
4.3 数据模型的可扩展性设计
模型建完不是一劳永逸的。业务会变,数据量会涨,查询模式会改。怎么让模型扛得住?我总结了三个原则。
4.3.1 预留扩展字段
在维度表里预留5-10个ext_col_1到ext_col_n的字段,类型统一用字符串。业务方临时要加个属性,直接往里塞,不用改表结构。
-- 示例:预留扩展字段的维度表
CREATE TABLE dim_customer (
customer_id INT PRIMARY KEY,
customer_name VARCHAR(100),
-- 核心属性
registration_date DATE,
-- 预留扩展字段
ext_col_1 VARCHAR(255),
ext_col_2 VARCHAR(255),
ext_col_3 VARCHAR(255),
-- 元数据
created_at TIMESTAMP,
updated_at TIMESTAMP
);
小技巧:预留字段不要太多,10个以内就够了。太多会导致存储浪费,而且容易变成垃圾场。我一般会在文档里注明每个扩展字段的用途,避免滥用。
4.3.2 使用桥接表处理多对多关系
维度建模里最头疼的就是多对多关系。比如一个商品属于多个品类,一个用户有多个标签。这时候别硬塞,用桥接表。
-- 桥接表示例:商品与品类
CREATE TABLE bridge_product_category (
product_id INT,
category_id INT,
is_primary BOOLEAN DEFAULT FALSE, -- 是否主品类
PRIMARY KEY (product_id, category_id)
);
这样做的好处是,新增品类时不需要改商品表,新增商品时也不需要改品类表。扩展性一下子就上来了。
4.3.3 分层设计:ODS -> DWD -> DWS -> ADS
这是数据仓库的经典分层,也是可扩展性的基石。每一层各司其职,互不干扰。
分层的好处很明显:ODS出了问题,不影响DWD;DWS改了汇总逻辑,不影响ADS。我见过不少团队把ETL逻辑全写在一层里,结果改一个指标要排查半天。
4.3.4 命名规范与元数据管理
这一点容易被忽视,但却是可扩展性的隐形支柱。我个人的习惯是:
- 表名:
层级_主题_业务过程_粒度,如dwd_trade_order_detail_di - 字段名:统一小写+下划线,避免大小写混用
- 注释:每个表和字段必须有中文注释,ETL逻辑也要写清楚
我曾经踩过的坑:有个项目没有统一命名规范,结果ODS层叫ods_user_info,DWD层叫dwd_user_detail,DWS层叫user_summary。后来业务方要查用户数据,光找表就花了半天。从那以后,我要求所有模型必须遵循命名规范,否则不让上线。
4.4 小结
数据建模没有银弹,但有套路。维度建模和事实建模各有各的用武之地,SCD策略要根据变化类型来选,可扩展性要靠分层、预留字段和规范来保障。
记住一句话:好的数据模型,是让业务方觉得「这数据就该这么组织」,而不是「这数据怎么这么难查」。