4、数据模型设计(上):维度建模理论

好,咱们进入数据集市最核心的部分——模型设计。说实话,我见过太多项目在建模阶段图省事,结果后面ETL跑不动、业务方看不懂、需求一变更就崩盘。维度建模不是银弹,但它是企业级数据集市最成熟、最经得起折腾的一套方法论。

这一章我们先啃理论,下一章再讲实战落地。嗯,先搭好骨架,再填肉。

4.1 维度建模的核心思想

维度建模说白了就两样东西:事实和维度。事实是“发生了什么”,维度是“谁、在哪、什么时候、什么产品”。

我习惯把事实表想象成账本,维度表就是字典。账本记录每一笔交易,字典告诉你这笔交易是谁买的、在哪个门店、卖的是什么商品。分开存,查起来快,改起来也方便。

核心原则:事实表尽量细,维度表尽量宽。细到原子粒度,宽到能覆盖所有分析角度。

4.2 星型模型 vs 雪花模型

这两个是维度建模最经典的两种形态。我刚开始做数仓时也纠结过选哪个,后来踩过坑才明白——没有的好坏,只有合不合适的场景。

4.2.1 星型模型

星型模型就是一张事实表在中间,周围一圈维度表直接连过来,像星星一样辐射出去。维度表不继续往下拆,每个维度都是扁平的一张表。

优点:查询快、理解简单、SQL好写。业务方自己都能拖拽出报表来。

缺点:数据冗余。比如“产品类别”这种属性,如果产品维度表里存了类别名称,那每个产品都重复存一遍。

我的经验:90%的数据集市场景,星型模型就够了。别为了省那点存储空间去搞雪花,存储便宜,人力和查询性能贵。

4.2.2 雪花模型

雪花模型就是把维度表继续规范化拆开。比如产品维度拆成产品表、类别表、品牌表,一层层连下去。

优点:数据一致性好,更新维护方便。改一个类别名称,只改一张小表就行。

缺点:查询要关联多张表,性能下降,SQL复杂度飙升。业务方基本看不懂。

对比维度 星型模型 雪花模型
查询性能 高(关联少) 低(关联多)
数据冗余 高 低
维护成本 低 高
业务理解 容易 困难
适用场景 OLAP查询、报表 数据治理、ETL中间层

避坑指南:我曾经在一个金融项目中强行用雪花模型,结果业务方每次提需求都要拉着我解释表结构。后来全部改回星型,虽然存储多了30%,但开发效率提升了不止一倍。

4.3 事实表设计

事实表是数据集市的“心脏”。你想想看,所有分析最终都要落到事实表上。事实表设计错了,后面全白搭。

事实表分三种类型,每种解决不同的问题。我按使用频率从高到低来讲。

4.3.1 事务事实表

这是最常用的一种。记录每一笔业务事件,比如每笔订单、每次登录、每笔转账。一行就是一次事件。

特点:

  • 粒度最细,一行一个事件
  • 只记录发生那一刻的事实
  • 数据量最大,但最灵活
-- 订单事务事实表示例
CREATE TABLE fact_order (
  order_sk      BIGINT,       -- 代理键
  order_id      VARCHAR(50),  -- 业务主键
  customer_sk   INT,          -- 客户维度外键
  product_sk    INT,          -- 产品维度外键
  store_sk      INT,          -- 门店维度外键
  date_sk       INT,          -- 日期维度外键
  order_amount  DECIMAL(10,2),-- 订单金额
  order_qty     INT,          -- 订单数量
  discount_amt  DECIMAL(10,2) -- 折扣金额
);

我的习惯:事务事实表一定要保留业务主键(比如order_id),方便溯源和对账。别为了省空间把业务主键丢了,后面查数据问题时会哭的。

4.3.2 周期快照事实表

这种表记录某个时间点的状态。比如每天统计一次库存余额、每月统计一次账户余额。它不关心中间发生了什么,只关心“到这一刻为止是什么样”。

适用场景:

  • 库存快照(每天盘点一次)
  • 账户余额(每月初记录)
  • 用户等级(每周统计)
-- 库存周期快照事实表示例
CREATE TABLE fact_inventory_snapshot (
  date_sk       INT,          -- 快照日期
  product_sk    INT,          -- 产品维度外键
  warehouse_sk  INT,          -- 仓库维度外键
  onhand_qty    INT,          -- 当前库存量
  reserved_qty  INT,          -- 已预留量
  available_qty INT           -- 可用量
);

关键点:周期快照的粒度是“时间周期 + 维度组合”。比如每天每个产品每个仓库一条记录。周期越长,数据量越小,但分析精度也越低。

4.3.3 累积快照事实表

这种表记录一个完整流程从开始到结束的累计状态。比如一笔订单从下单、支付、发货、到签收的全过程。一行就是一个流程实例,字段会随着流程推进不断更新。

特点:

  • 一行代表一个完整流程
  • 字段会更新(不像事务事实表只追加)
  • 适合分析流程耗时、瓶颈环节
-- 订单累积快照事实表示例
CREATE TABLE fact_order_accumulating (
  order_sk        BIGINT,
  order_date_sk   INT,          -- 下单日期
  pay_date_sk     INT,          -- 支付日期(初始为空)
  ship_date_sk    INT,          -- 发货日期(初始为空)
  receive_date_sk INT,          -- 签收日期(初始为空)
  order_amount    DECIMAL(10,2),
  -- 时间间隔字段(ETL计算)
  pay_to_ship_days INT,
  ship_to_receive_days INT
);

避坑指南:我曾经做过一个物流项目,累积快照表里放了十几个时间字段,结果ETL脚本写得像意大利面条。后来我定了个规矩——累积快照表的时间字段不要超过8个,超过就拆成多个事实表或用明细表补充。

4.4 维度表设计

维度表是事实表的“翻译官”。事实表里存的都是ID,维度表告诉你这些ID是什么意思。维度设计得好,业务方自己就能看懂数据。

4.4.1 退化维度

退化维度就是把维度属性直接塞进事实表里。比如订单号、发票号、物流单号——这些维度通常只有一个属性,单独建一张维度表反而浪费。

什么时候用?

  • 维度只有一个属性(比如订单号)
  • 维度属性很少(比如发票号+发票类型)
  • 查询时经常需要这个字段做过滤或分组
-- 退化维度示例:订单号直接放在事实表中
CREATE TABLE fact_order (
  order_id    VARCHAR(50),  -- 退化维度
  customer_sk INT,
  product_sk  INT,
  amount      DECIMAL(10,2)
  -- 不需要单独建dim_order表
);

我的经验:退化维度用得好,能减少30%以上的表关联。但别滥用——如果订单号还需要关联订单的其他属性(比如订单来源、订单类型),那就老老实实建维度表。

4.4.2 角色扮演维度

同一个维度表,在事实表里扮演多个角色。比如“日期”维度,在订单事实表里可能是“下单日期”、“支付日期”、“发货日期”。你不可能为每个角色建一张日期表,太浪费了。

实现方式:在事实表里用不同的外键字段指向同一张维度表。

-- 角色扮演维度示例:日期维度扮演多个角色
CREATE TABLE fact_order (
  order_sk        BIGINT,
  order_date_sk   INT,  -- 指向dim_date,角色:下单日期
  pay_date_sk     INT,  -- 指向dim_date,角色:支付日期
  ship_date_sk    INT,  -- 指向dim_date,角色:发货日期
  customer_sk     INT,
  amount          DECIMAL(10,2)
);

-- 查询时用别名区分角色
SELECT 
  d1.date_str AS order_date,
  d2.date_str AS pay_date,
  d3.date_str AS ship_date
FROM fact_order f
JOIN dim_date d1 ON f.order_date_sk = d1.date_sk
JOIN dim_date d2 ON f.pay_date_sk = d2.date_sk
JOIN dim_date d3 ON f.ship_date_sk = d3.date_sk;

核心要点:角色扮演维度是复用,不是复制。一张维度表可以同时服务多个角色,但每个角色在事实表里要有独立的字段。别想着用同一个字段存多个日期,那是在给自己挖坑。

4.5 本章知识体系总览

下面这张图把维度建模的核心结构串起来了。我建议你保存下来,后面做设计时对照着看。

维度建模核心结构图 事实表 度量值 + 外键 事务事实表 每笔事件一条记录 周期快照事实表 定期记录状态 累积快照事实表 流程累计状态 维度表 描述性属性 退化维度 属性直接放事实表 角色扮演维度 同一维度多角色 设计原则 事实表尽量细粒度,维度表尽量宽表 90%场景用星型模型,避免过度规范化

这张图把三种事实表和两种维度设计方式都串起来了。你注意看,事实表是中心,维度表是外围。事务、周期、累积三种事实表解决不同粒度的分析需求;退化维度和角色扮演维度解决维度表的设计效率问题。

总结一句话:维度建模不是死记硬背的教条,而是一套“事实+维度”的思维框架。你掌握了这个框架,后面不管遇到什么业务场景,都能快速拆解出模型结构。