
我们在做数据开发的时候不管是跟着教程搭数仓还是在公司里接手一个真实的数据平台项目绕不开一对核心概念ER模型实体关系模型和维度模型维度建模。我是跟着尚硅谷那套数仓搭建课程一步一步走下来的学到建模这一章的时候说实话第一次看到为什么数仓选择维度模型而不是ER模型这个问题整个人是懵的。因为在大学数据库课上老师反复强调的规范化、三范式、消除冗余到了数仓这里好像突然被推翻了。这篇笔记就把我在这个阶段整理出来的理解、对比和踩坑记录做个梳理希望能帮你把这块最基础也最关键的建模逻辑彻底捋顺。1. ER模型业务系统的原配背了一身包袱1.1 三范式到底在做什么ER模型全称是Entity-Relationship也就是实体关系模型。它诞生的场景和数仓完全不同。它的服务对象是OLTP系统也就是在线事务处理系统像你每天都在用的订单系统、支付系统、会员管理系统背后几乎都是ER模型的数据库设计。这类系统的核心诉求是在大量并发写入的情况下保证数据不丢、不错、不乱。为了做到这一点设计上要做的一件大事就是消除数据冗余。消除冗余靠的是规范化理论也就是我们常说的三范式。第一范式要求字段不可再分比如地址不能拆成一长串字符串里藏着省份和城市应该拆成独立的字段。第二范式要求非主键字段必须完全依赖主键不能只依赖主键的一部分这在联合主键的表中尤其典型。第三范式要求非主键字段之间不能存在传递依赖比如用户所属部门这个字段如果出现在用户表里而部门的属性又依赖部门ID那就产生传递依赖了应该拆成单独的表。用一个电商场景来说更直观。假设你有一套业务库里面有用户表、订单表、商品表、门店表、区域表每张表都按范式要求拆开通过外键关联。这种设计的直接好处是一份数据只存一处更新用户手机号只需要改一行不会出现同一个手机号在十几个地方存着、要改就得改十几处的噩梦。所以从OLTP的角度看ER模型是很好的方案它最大程度保证了数据一致性和更新效率。1.2 为什么ER模型在分析场景会卡壳问题出在读上面。ER模型的设计目标是把写操作做到极致可数据分析恰恰是另外一种截然不同的工作模式。分析类查询的特点是扫描数据量大、聚合操作多、维度筛选复杂而且对响应速度有要求。你把一个第三范式的库丢给数据分析师很容易出现以下三个问题。第一个问题是查询要关联的表太多。做一次销售分析可能需要从订单表出发关联用户表、商品表、门店表、区域表、优惠券表、支付表一张分析SQL动辄就是五六个JOIN。关联多了查询计划就复杂在数据量大的时候性能肉眼可见地下降。你不要觉得数据库会自动优化在大数据量、多表关联的场景下优化器能做的其实非常有限。第二个问题是业务人员根本看不懂。ER模型下的表结构是按照消除冗余的规则拆出来的不是按照业务理解组织出来的。销售部门想看一下华东区3月份卖了多少他得知道订单表、区域表、时间维度怎么关联得搞懂哪些字段是事实、哪些是维度这对业务同学来说几乎是不可能的哪怕对刚接触数据的开发同学来说也得花不少时间摸清楚表之间的关系。第三个问题是口径还原成本高。同样一个销售额业务系统里可能有下单金额支付金额退款后金额好几种分散在好几张表里。如果你直接从ER模型里面取数不同的分析师写的SQL可能口径各不相同出来的结果对不上最后谁都说不清楚哪个数是对的。这就是典型的数据仓库建成一张蜘蛛网的窘境。注意我说这些不是要否定ER模型。ER模型在OLTP系统里依然是最优解数仓的源系统几乎都是ER模型的库。数仓不直接复用ER模型恰恰是因为它的设计目标和分析场景不匹配而不是它本身有错。这个定位要先摆正不然后面学起来容易拧巴。2. 维度模型面向分析量身定制2.1 事实表和维度表一对天生的搭档如果说ER模型是为了写而生那维度模型就是为了读而生。维度建模的思路可以追溯到Ralph Kimball他的核心理念非常简单把业务过程描述成一组由事实和维度构成的表。事实表记录的是业务过程中的度量值也就是可以加总、求平均、比较大小的数值。每一行事实表对应一次业务事件。在电商场景里一次下单就是一个事件事实表里存的是谁在什么时候在哪个门店买了什么商品花了多少钱。维度表则是描述业务事件的上下文用来回答谁、什么、哪里、何时、为什么。用户维度告诉你买家是谁商品维度告诉你买的是什么门店和区域维度告诉你发生在哪里时间维度告诉你发生在什么时候。这两者放在一块就像超市里的一本销售台账和一本商品名册。事实表是流水账一条一条记录每个顾客买了几件商品花了多少钱维度表是商品分类和会员资料用来解释流水中每一条记录的具体含义。两者通过一个维度主键关联维度表相对小而稳定事实表则不断增长。维度建模里还有几个必须掌握的关键概念。代理键也就是维度表里独立于业务主键的自增主键它的作用是解耦业务系统的变更比如用户ID在业务库里被回收复用但维度表里的代理键永远不会变。维度属性就是维度表中可以用于分组、筛选和标记的描述性字段比如用户的性别、年龄段、会员等级。还有缓慢变化维度的处理这是数仓里非常经典的一块话题后面单独展开说。2.2 星型模型和雪花模型两种风格的取舍维度模型在落地的时候最常见的是星型模型和雪花模型两种形式。星型模型是所有维度表直接连接在事实表周围从结构上看像一颗星星中间是事实表四周是维度表。这种模型的优点是查询路径短事实表关联维度表时每个维度只需要一次JOIN优化器容易处理查询性能好。代价是维度表可能存在部分冗余比如商品维度里直接冗余了品牌名和品类名而不需要再去关联品牌表和品类表。雪花模型则是对维度表进行再一次规范化。比如商品维度表里的品类字段拆成一张品类维度表再通过外键关联回来。从结构上看像雪花分叉减少了数据冗余但代价是查询时的JOIN次数变多了ETL链路变长了性能也受到一定影响。所以在实际数仓建设中星型模型是绝对的主流雪花模型在一些特定场景下才会用到比如维度层次非常分明、且对存储成本特别敏感的场景。对比项星型模型雪花模型查询性能高JOIN次数少较低维度表JOIN层数多数据冗余维度表存在冗余冗余低规范化程度高ETL复杂度简单直接较复杂需要维护的依赖多易用性业务人员易理解表结构较复杂理解成本高适用场景数仓主流选择分析性能优先特定场景层级明确且需节省存储2.3 维度建模的四个基本步骤Kimball的维度建模方法其实有一整套流程核心可以概括为四步选择业务过程、声明粒度、确认维度、确认事实。选择业务过程就是确定你要分析哪一个业务事件。电商里下单支付发货退款都是独立的业务过程每个业务过程都要建对应的事实表。这里特别强调一个关键认知一个业务过程一张事实表而不是把好几个过程塞进一张大宽表。把下单、支付、退款全部混在一张表里看起来大而全实际上后续的口径、粒度和使用都会变得混乱。声明粒度是建模中最关键、最容易出错的一步。粒度指的是事实表中一行数据代表什么级别的业务事件。比如订单事实表一行可以是一个订单的一件商品子项也可以是整个订单还能是每天每个商品的汇总值。粒度一旦定错后续所有分析都会错所以这步必须在建模前跟业务方对齐清楚而且尽量往细粒度设计细粒度可以向上汇总出粗粒度反过来不行。确认维度和确认事实就相对顺理成章了。维度是你要从哪些角度分析这个业务过程还是拿下单举例人用户、物商品、地门店/区域、时时间、渠道这些都是维度。事实则是度量值下单金额、商品数量、运费、优惠金额。这两步做完数据模型的核心骨架就出来了。3. 数仓为什么选维度模型四个理由逐个拆3.1 理由一查询性能维度模型天生为聚合优化这是最直接的原因。数仓的核心场景是分析而分析天然依赖聚合。维度模型把表结构设计成了事实表维度表的组织方式在查询的时候能做到让优化器走最短路径。我们先不看高深的理论做个直观对比。同样的华东区3月份销售额用ER模型的思路可能需要关联的表的路径是订单信息→订单明细→用户→地址→区域再加商品→类目一张统计SQL写下来七八个JOIN是家常便饭。而用维度模型你的事实表里直接存了区域维度的代理键一条查询只需要关联区域维度表和事实表。-- ER模型风格查询华东区3月销售额需要从订单明细一路关联到区域 SELECT SUM(oi.product_amount) FROM order_item oi INNER JOIN orders oo ON oi.order_id oo.order_id INNER JOIN customer c ON oo.customer_id c.customer_id INNER JOIN address a ON oo.address_id a.address_id INNER JOIN region r ON a.region_id r.region_id INNER JOIN product p ON oi.product_id p.product_id WHERE r.region_name 华东区 AND oo.order_time 2024-03-01 AND oo.order_time 2024-04-01; -- 维度模型风格事实表直接通过维度外键关联 SELECT SUM(f.order_amount) FROM fact_order f INNER JOIN dim_region r ON f.region_key r.region_key WHERE r.region_name 华东区 AND f.order_date_key BETWEEN 20240301 AND 20240331;不要小看这个差别。在数据量只有几十万条的时候两种写法的性能差距感知不明显一旦事实表到了几亿行每多一次JOIN都是扫描和shuffle的巨大开销。在实际的数仓平台里很多基于维度模型的事实表还会做预聚合比如按天预先汇总好每天的销售数据查询3月的汇总值连事实表都不用扫直接读汇总表。而ER模型要做预聚合你得先搞清楚表之间的依赖关系整个ETL逻辑复杂得多。性能这个理由在实际生产环境里是压倒性的。3.2 理由二业务人员真的能看懂数据仓库的最终用户是业务人员不是只有数据工程师。如果建出来的数仓只有工程师自己会用那这个数仓的落地价值就要打大大的折扣。维度模型在这点上有着天然的优势它的表结构就是按照业务的分析视角来设计的。事实表对应发生了什么业务事件维度表对应这个事件的各种属性。业务人员想要按渠道统计订单金额他只需要知道在订单事实表里按渠道维度表分组求和即可。哪怕是完全没学过数据库的人给他看一张订单事实表和几张维度表他也能大概联想出怎么用。这就是维度模型被称为用户友好模型的原因。我之前在公司做过一个小调研让一个运营同学看ER模型的库和维度模型的数仓各写一条最近7天各渠道订单量的查询逻辑。ER模型那边他根本不知道从哪个表开始维度模型这边他很快就指出来在这里选日期、渠道、加总订单笔数就可以了。这件事让我印象很深好的数据模型应该像一个友好的业务工具而不是一个需要破解的迷宫。这一点决定了维度模型是面向业务的而ER模型本质上是面向开发的。数仓作为一个平台服务的是大量不懂技术的业务用户它必须在易用性上做出明确的选择。这也是数仓领域把维度建模作为事实标准的最重要原因之一。3.3 理由三应对需求变化的弹性业务分析的需求不是一成不变的。今天要看区域销售额明天可能要看会员等级对复购率的影响后天要加一个新渠道。维度模型对这类需求变化的响应速度非常快。比如当前的事实表里已经有用户维度、商品维度、门店维度、时间维度。业务方突然提了一个新需求按库存类型分析一下销售结构。你只需要在商品维度表里增加一个库存类型字段事实表结构完全不用动ETL只需要更新商品维度表即可开发量很小。反过来如果需要分析一个新的度量值比如毛利你只需要在事实表里增加一个毛利金额字段维度表也不用动。这种维度加字段、事实加度量的扩展模式让变更影响范围被控制在很小的范围里。而如果用ER模型要支持一个新的分析角度往往需要新增表、修改外键关系、调整一系列JOIN逻辑工作量和风险都大得多。再说说探索式分析。业务人员经常会问我能不能顺便看看这个维度下不同类别的情况在维度模型下这种临时需求很容易满足因为维度属性天然都在那里在ER模型下就得重新梳理关联关系响应速度完全不是一个量级。3.4 理由四ETL和生命周期管理更干净数据仓库里永远不只是静态的表结构还有一套持续运转的ETL流程。维度模型在ETL的职责划分上非常清晰事实表是追加型数据每天增量写入新数据历史数据基本不变适合分区管理维度表是更新型数据业务属性发生变化时需要做缓慢变化维的处理比如保留历史、覆盖当前、或者同时保留多条记录。这种事实追加、维度更新的模式和数仓的分层架构配合得非常好。在ODS层到DWD层你做的操作主要是清洗和规范化在DWD层到DWS层维度模型开始发挥作用通过维度建模组织成事实表和维度的形式再往上的ADS层可以直接基于维度模型输出各种数据集市。每一层做什么、怎么流转责任边界清晰开发和维护的效率都大大提高。很多大厂的数仓建设体系比如阿里早期的OneData体系、后来广泛使用的各类数仓规范核心思路都建立在维度建模的基础上。拿阿里来说它提出的明细模型概念本质上就是把业务过程的事实明细表按照统一粒度、统一口径组织起来在公共层建立一致性的维度和事实这一套方法论和Kimball的维度建模是高度一致的。美团、快手等公司的数仓团队在实践分享中也都会提到事实表维度表的建模框架。可以说在互联网行业的数据开发实战里维度模型已经是公认的主流域市。4. 学习过程中的常见混淆点与实操体会4.1 学完概念之后依然容易踩的几个坑概念上理解了ER模型和维度模型后我在动手做数仓搭建项目时还是踩了几个坑。这里整理一下希望能帮你绕过去。第一个坑是数仓也要做三范式因为数据库课上是这么教的。这个想法很顽固。我一开始甚至试图在数仓里做一个3NF的模型然后发现SQL写起来异常痛苦一个报表需求要关联七八张表性能还差。后来才真正想通数仓不追求减少写入异常而是追求加速读取和分析。范式化的目标在数仓里没有意义反而不是最优解。第二个坑是维度模型就是做宽表把所有字段都塞进去。这是对维度模型的误解。宽表确实是维度建模的一种表现形式比如DWS层的汇总宽表把多个维度、多个度量都揉在一起目的是减少重复JOIN。但真正规范的维度模型是事实表和维度表分离的宽表只是面向特定查询场景的一种物理优化手段不能等同于维度建模本身。第三个坑是忽略维度表的设计。我只顾着设计事实表想着把度量值定义清楚就行结果维度表随便建了几个字段后面发现业务方想要按用户年龄段分析的时候根本没有对应字段还得回头补维度表。维度表是分析的角度和入口它的属性和层级设计要花大力气很多时候维度表的设计质量直接决定整个分析体系的上限。4.2 面试追问时的高频考点这块知识点在数据开发岗位的面试中相当高频。面试官一般不会只问什么是维度模型而是会往上追问几个层次。第一个常见追问就是ER模型和维度模型的核心区别是什么可以围绕设计目标OLTP vs OLAP、表结构范式化 vs 事实表维度表、适用场景写入频繁 vs 分析聚合三个维度回答。第二个追问是为什么数仓不直接用业务库的ER模型做分析这就是考察你对性能、易用性、口径统一的理解。第三个追问是讲讲你对三种数据仓库建模理论的理解这里除了范式建模和维度建模还有一种Inmon倡导的第三范式数据集市的范式建模路线这和维度建模的侧重点不同但主流的互联网数仓更偏向维度模型原因是实现周期短、见效快。提示面试时如果能把一个业务过程对应一张事实表粒度的选择决定了汇总口径的边界维度建模应对变化快于范式建模这几个细节讲清楚面试官基本就能判断你是扎实学过而不是背概念。4.3 后续可以继续深挖的方向掌握ER模型和维度模型的区别只是数仓建模的入门关。往后深入学习还有几个紧密相关的方向值得花时间。一个是缓慢变化维度SCD业务维度属性变了怎么办是直接覆盖、保留历史、还是渐变保留多条每种策略的适用场景和代价是什么。这个是维度模型落地的必答题因为业务系统里的用户等级、门店归属、商品类目无时无刻不在变化。另一个是事实表的分类事务事实表、周期快照事实表和累积快照事实表的区别。订单明细是事务事实表每日库存快照是周期快照事实表订单从下单到支付到发货到完成的全程状态是累积快照事实表。这些概念会直接影响你对DWD和DWS层表结构的设计。我个人在实际学习中的体会是建模不是一个纯理论问题而是一个工程取舍问题。ER模型不是不好而是它在分析场景下的保持一致性的代价太高。维度模型也不是没有缺点它存在冗余、存在同步一致性的问题但它在多快好省地获得分析结果这个数仓核心诉求上几近最优。这个取舍逻辑想明白之后再看整个数仓搭建项目的分层设计、表结构设计和ETL流程都会有一种豁然开朗的感觉。最后再分享一个小技巧初学建模的时候不用急着背各种概念先拿一个真实业务过程比如电商下单从选择业务过程开始抄一遍四步建模法理解事实表里每一行代表什么、维度表里每个字段回答什么比你多看十篇文章都管用。