03.维度建模入门:星型模型 vs 雪花模型 vs 星座模型【现实版】
你左手拿着建模理论,右手对着自己公司的数据仓库,会发出一个疑问,我这到底是星型模型、雪花模型、星座模型中的哪一个?
你会发现你根本没有办法回答。
以星型模型为目标,允许
1.1 逻辑二维表
数据仓库中,数据以表的形式存在,形式如下:

称为“逻辑表”,主要原因是在实际存储的时候,不一定是按照我们看到的形式存储的,会有各种形式的存储优化(例如列存)。 但是在建模的时候,二维表是我们思考的基本单位。
数据仓库建模就是 定义这些表的内部结构和表之间的关联关系。 一张表属于什么表,取决于其存储的内容,而不是其表名。表名只是方便我们识别和管理,而不是决定表的类型。
重要的事情再讲一遍: 无论是 维度表、事实表、桥接表、宽表、竖表、全量表、增量表、拉链表,我们都是在讲其存储的内容,内容的组织形式,而不是表的名字,表的名字是为了方便方便识别和管理。
1.2 星型模型、雪花模型、星座模型 在讲什么
- 这三种模型属于维度建模方法,不是独立的建模方法论;
- 这三种模型描述的是二维表的关联形态,其描述的很形象,表关联起来的图形像什么就叫什么模型。(思考:三种模型描述了表关联形式,但是表为什么要关联、表什么时候关联?)
- 表内部结构决定了关联形态,关联形态决定了如何设计表内部结构。
二、先理解维度建模的两类表
讲模型之前,必须先分清维度建模里的两类表,分别是事实表和维度表。
| 表类型 | 存什么 | 特点 | 举例 |
|---|---|---|---|
| 事实表 (Fact) | 业务过程产生的可度量数值 | 行数巨大、持续增长、窄而长 | 订单金额、下单数量 |
| 维度表 (Dimension) | 描述业务事件的上下文属性 | 行数少、相对稳定、宽而短 | 用户、商品、时间、地区 |
事实表回答「发生了什么,量是多少」,维度表回答「发生在谁身上、什么时候、什么渠道」。两者通过外键关联:事实表里存一堆维度表的主键,查询时 JOIN 起来。
┌──────────────┐
│ dim_user │ ← 维度表(谁)
│ user_id(PK) │
│ user_name │
└──────┬───────┘
│ user_id
┌──────▼───────┐
│ fact_order │ ← 事实表(发生了什么)
│ order_id │
│ user_id(FK) │
│ product_id │
│ amount │ ← 度量(可加)
└──────┬───────┘
│ product_id
┌──────▼───────┐
│ dim_product │ ← 维度表(什么)
│ product_id │
│ category │
└──────────────┘
二、星型模型 (Star Schema)
2.1 长什么样
星型模型是最经典、最常用的维度模型。它的结构是:一张事实表居中,周围一圈维度表直接连上来,没有中间层,形似星星。
dim_date
│
dim_user ──── fact_order ──── dim_product
│
dim_region
特征:
- 一张事实表 + N 张维度表,全部通过外键直接关联
- 维度表之间不互相连接
- 维度表不规范化(允许冗余),比如
dim_user里同时存city和province,而不是拆成地区维度
2.2 为什么是主流
星型模型被 Kimball 学派捧为「默认选择」,核心就一句话:查询简单、性能好。
- JOIN 少:任何分析都只需事实表 + 少量维度表,最多
N+1张表 JOIN,通常 2-3 张 - SQL 直观:分析师一眼能看懂,不需要理解复杂的规范化层级
- 列存友好:宽维度表在列存引擎下压缩率高、扫描快
-- 星型模型下,查「各城市订单总额」只需 2 张表
SELECT r.province, SUM(f.amount) AS total_amount
FROM fact_order f
JOIN dim_region r ON f.region_id = r.region_id
WHERE f.order_date >= '2026-01-01'
GROUP BY r.province;
2.3 代价
维度表不规范化,意味着冗余。dim_user 里 province 字段在 1000 万用户里重复出现,占存储。不过在现代列存 + 字典编码下,这种冗余的成本几乎可以忽略——这也是星型「赢」了雪花的关键原因之一。
三、雪花模型 (Snowflake Schema)
3.1 长什么样
雪花模型是星型的「规范化版本」:维度表继续往下拆成多级子维度表,形成层级,形似雪花。
dim_date
│
dim_user ──── fact_order ──── dim_product
│ │
dim_city ── dim_province dim_category ── dim_brand
比如 dim_user 不再存 city、province,而是只存 city_id,然后 city 又指向 province。维度表被拆到第三范式(3NF)。
3.2 优点
- 消除冗余:地区、分类这类层级信息只存一份
- 维护一致性:改了
dim_province的名字,所有下游自动生效,不会出现「同一省份两种写法」
3.3 为什么实际很少用
雪花模型在理论上「更干净」,但工程上几乎被星型碾压:
| 维度 | 星型 | 雪花 |
|---|---|---|
| JOIN 数量 | 少 | 多(每级子维度多一个 JOIN) |
| 查询性能 | 快 | 慢(JOIN 是数仓最大开销) |
| SQL 复杂度 | 低 | 高(分析师要懂层级) |
| 存储冗余 | 有 | 无 |
| 维护成本 | 中 | 低(规范化) |
关键结论:雪花省下的那点存储,远不够抵消多出来的 JOIN 开销。维度表本身数据量就小(用户维度几千万行在数仓里算「小表」),冗余的代价极低;而 JOIN 是数仓查询的绝对瓶颈。
3.4 雪花模型的少数适用场景
雪花模型并非一无是处,两种情况下可以局部采用:
- 维度层级很深且查询频繁按层级钻取——比如地区有「国家→省→市→区」四级,且 BI 经常要按省、市逐层下钻
- 维度表本身巨大且层级稳定——规范化能明显省存储
但即便用,也通常只在局部雪花化(只有一两个维度拆),而不是全盘雪花。
四、星座模型 (Constellation Schema / 星系模型)
4.1 长什么样
星座模型(也叫星系模型、事实星座)是多张事实表共享维度表的结构,形似多个星型交叠在一起。
dim_date
/ \
dim_user ─ fact_order fact_payment ─ dim_channel
\ | |
\ dim_product ────────┘
\ |
fact_refund ── dim_region
注意:dim_user、dim_date、dim_product 被 fact_order、fact_payment、fact_refund 三张事实表共享。
4.2 为什么需要星座模型
真实数仓从来不是只有一张事实表。电商有订单事实、支付事实、退款事实、浏览事实、加购事实……它们共享同一套维度(用户、时间、商品、地区)。
星座模型解决的核心问题是:让不同业务过程的事实表,共享一致的维度,从而支持跨域分析(比如「各渠道的 GMV」= 订单事实 JOIN 渠道维度 + 支付事实 JOIN 渠道维度)。
4.3 核心:一致性维度 (Conformed Dimension)
星座模型能成立的前提是一致性维度:被共享的维度表,在所有事实表里定义完全一致(相同的字段名、相同的取值域、相同的语义)。
fact_order.user_id ┐
fact_payment.user_id ├── 都指向同一张 dim_user,且语义一致
fact_refund.user_id ┘
如果订单事实里的 user_id 和支付事实里的 user_id 不是同一套编码,JOIN 出来就是笛卡尔积垃圾。
这正是 数据仓库建模原则 里「维度一致才能跨域分析」这条原则的由来。
五、三种模型对比总表
| 维度 | 星型模型 | 雪花模型 | 星座模型 |
|---|---|---|---|
| 事实表数量 | 1 张 | 1 张 | 多张 |
| 维度表是否规范化 | 否(冗余) | 是(3NF 层级) | 取决于维度 |
| JOIN 数量 | 少 | 多 | 中 |
| 查询性能 | 高 | 低 | 高 |
| 存储 | 有冗余 | 省存储 | 有冗余 |
| 适用场景 | 单一业务过程 | 维度层级深的局部 | 企业级多业务过程 |
| 本质 | 基础形态 | 星型的规范化变体 | 多个星型共享维度 |
一句话记忆:
- 星型 = 一张事实表 + 一圈维度
- 雪花 = 星型里维度继续往下拆
- 星座 = 多张事实表共享同一批维度
六、选型决策:你到底该建哪种
你要建几个业务过程的模型?
├── 一个 → 维度表层级深、需要严格钻取吗?
│ ├── 否 → ✅ 星型模型(默认选择)
│ └── 是 → ⚠ 局部雪花(只拆层级最深的 1-2 个维度)
└── 多个 → ✅ 星座模型(共享一致性维度)
└── 每个业务过程内部仍按「星型」建
实战经验:
- 默认星型,别纠结。星型是经过 20 年验证的工业标准
- 多业务过程必然星座。订单、支付、退款三个事实表天然要共享用户/时间/商品维度
- 雪花只在局部出现,且通常是「地区」这种层级深、又高频钻取的维度
- 别追求范式。数仓是「为查询优化」的,不是「为存储优化」的
七、维度建模四步法(附赠)
无论选哪种模型,建模过程都遵循 Kimball 的四步法:
- 选择业务过程——先确定「你要描述哪个动词」:订单?支付?登录?
- 声明粒度——回答「事实表一行代表什么」:一行 = 一个订单?一行 = 一个订单明细?
- 确定维度——这一行涉及哪些「谁、何时、何地、何物」?
- 确定事实——这一行有哪些「可度量的数值」?
粒度是四步法里最容易被跳过、也最致命的一步。粒度错了,后面字段全错。详见 数据仓库建模原则 的「一张表只表达一种粒度」。
八、常见坑点
- 把雪花当「更高级」——恰恰相反,雪花是「为了省存储牺牲查询」,而数仓最不在乎的就是那点存储
- 星座模型里维度不一致——三张事实表各建各的
dim_user,跨域分析全废。共享维度必须是一张表 - 粒度混用——订单级和订单明细级塞进同一张事实表,度量怎么 SUM 都是错的
- 维度表里塞事实——把「订单金额」放到
dim_user里,维度表退化成事实表,JOIN 就炸了
九、小结
- 星型模型:一张事实表 + 一圈维度,查询快、SQL 简单,是默认选择
- 雪花模型:维度规范化,省存储但 JOIN 多,实际少用,只在局部出现
- 星座模型:多事实表共享一致性维度,企业级多业务过程的必然形态
三者不是互斥关系:一个成熟数仓通常是「星座为骨架、每个事实都是星型、局部雪花」,核心诉求只有一个——让查询简单、让维度一致。
📌 本文是 dwsql.com 数仓建模方法论系列第 1 篇。下一篇讲事实表:事务事实表、周期快照事实表、累积快照事实表到底有什么区别,见 事实表完全指南。
「数据仓库技术」文章同步更新,不错过每一篇干货

备注「数据仓库技术」加入社群,每日一道大厂SQL真题

