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

这是数据仓库的经典分层,也是可扩展性的基石。每一层各司其职,互不干扰。

数据模型分层架构 ADS 应用数据层 报表、大屏、自助分析 DWS 汇总数据层 轻度汇总、宽表、主题集市 DWD 明细数据层 清洗、去重、维度退化 ODS 操作数据层 源系统数据、增量/全量同步 业务数据库 日志/埋点 外部接口 分层设计的好处 • 解耦:每层独立变更 • 复用:下层为上层服务 • 可追溯:数据血缘清晰 • 性能:各层可独立优化

分层的好处很明显: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策略要根据变化类型来选,可扩展性要靠分层、预留字段和规范来保障。

记住一句话:好的数据模型,是让业务方觉得「这数据就该这么组织」,而不是「这数据怎么这么难查」。