数据仓库为什么一定要分层?把 ODS、DWD、DWS、ADS 一次讲透
导读 / Key Takeaways
- 数据仓库分层的核心,不是记住
ODS/DWD/DWS/ADS这些缩写,而是用清晰边界管理数据资产生产过程。ODS负责保留原始真相,DWD负责沉淀可复用明细,DWS负责沉淀公共汇总,ADS负责面向业务交付结果。- 真正决定分层成败的,不是层名,而是原始层是否可追溯、明细层是否粒度清晰、公共层是否口径统一、应用层是否只做最后一跳。
- 文章会用一个具体的电商订单案例,把源表、维度、明细、汇总和应用层的设计链路完整走一遍。
- 如果你正在搭数仓,可以先记一句话:先保留原始真相,再围绕清晰粒度沉淀公共事实和维度,最后把结果交给业务使用。
很多团队一聊数据仓库分层,都会顺手写出一串缩写:ODS -> DWD -> DWS -> ADS。看久了,很多人会产生一种错觉,仿佛这只是行业里的命名习惯,背下来就够了。
但真正做过数仓的人通常都知道,问题没这么简单。
一个团队一旦开始接触多业务线、多个报表口径、多个消费场景,数据链路很快就会失控:原始数据和结果表混在一起,指标到处重复算,报表口径彼此对不上,需求一变就得全链路返工。到最后,大家表面上还在说“我们有数仓分层”,实际上系统里只有一堆名字看起来像分层的表。
所以,数据仓库分层的重点,从来都不是记住几个缩写,而是回答一个更本质的问题:
怎样把数据从“原始输入”变成“稳定可复用的数据资产”,而且这个过程还能长期维护下去?
这篇文章就把这件事完整讲清楚。
先说结论:分层不是为了好看,而是为了控制复杂度
数据仓库分层,本质上是在解决四类问题:
- 原始数据要可追溯,出了问题能回放。
- 明细加工要可复用,不能每个报表都重写一套逻辑。
- 公共口径要能收敛,不能同一个指标有三四种算法。
- 面向业务的结果要足够稳定,报表、接口、标签系统不能反复扫底层明细。
如果只用一句话概括,分层的意义就是:
把“数据处理过程”变成“可治理的数据资产生产线”。
没有分层时,最常见的症状通常是:
- 原始数据和结果数据放在一起,责任边界不清
- 同一指标在多个任务里重复开发
- 报表之间口径打架,谁都说自己对
- 上层应用直接依赖底层明细,链路耦合越来越重
- 源系统修历史数据后,平台无法稳定重算
这些问题表面上看是开发效率问题,本质上其实是边界问题。
传统数仓分层,到底在分什么?
在国内数据仓库实践里,最常见的是这五层:
ODS:原始数据接入层DIM:维度层DWD:明细数据层DWS:汇总数据层ADS:应用数据服务层
很多公司虽然实现细节不同,但这套思路基本一致。因为它刚好对应了数据从接入到消费的几种核心职责。
如果换到现代湖仓语境,也可以把它和 Bronze -> Silver -> Gold 做一个工程上的近似理解:
| 传统数仓 | 现代湖仓近似对应 | 主要职责 |
|---|---|---|
| ODS | Bronze | 原始接入、保留真相 |
| DWD + 部分 DIM | Silver | 清洗、校验、标准化、形成可复用明细 |
| DWS + ADS | Gold | 面向主题分析、聚合、业务消费 |
这里要强调一点:这不是严格的一一映射,但它们在设计目标上非常接近。传统数仓更强调主题建模和稳定服务层,湖仓更强调统一存储和逐层提纯,本质上仍然是在做同一件事。
为什么很多团队“看起来有分层”,最后还是一团乱?
因为他们分的是名字,不是职责。
真正有效的分层,核心不在于层名对不对,而在于下面这些边界有没有守住:
- 原始层是不是还保留原始真相
- 明细层是不是有清晰粒度
- 公共层是不是沉淀了真正可复用的口径
- 应用层是不是只做最后一跳交付
只要这些边界塌掉,再标准的 ODS/DWD/DWS/ADS 也会退化成“看起来很规范的中间表堆”。
用一个电商订单案例,把分层一次讲清楚
抽象概念最好理解,但最难真正用起来。要把分层讲透,最好的办法还是看一个完整例子。
假设你现在要为电商业务搭一套订单分析数仓,源系统里有这些表:
mysql.order_info:订单主表,包含order_id、user_id、shop_id、order_status、create_timemysql.order_item:订单商品明细表,包含order_item_id、order_id、sku_id、qty、origin_amountmysql.payment_info:支付表,包含pay_id、order_id、pay_amount、pay_timemysql.refund_info:退款表,包含refund_id、order_id、refund_amount、refund_status
如果这套链路设计合理,整个分层关系可以长这样:
这张图里最关键的不是层数,而是两件事:
- 明细事实来自原始层,但会引用统一维度。
- 应用层尽量站在公共层之上,而不是绕回去直接扫原始表。
下面我们按层拆开来看。
ODS:先把源数据稳稳接住
ODS 通常是数据进入分析体系后的第一站。它最重要的任务,不是做复杂业务加工,而是保留原始语义、接住历史变化、提供可追溯能力。
在电商订单场景里,ODS 往往会有:
ods_order_infoods_order_itemods_payment_infoods_refund_info
以 ods_order_item 为例,一般至少会保留这些字段:
order_idorder_item_idsku_idqtyorigin_amountdtetl_time
你会发现,这一层并不追求“好分析”,而是追求“别把真相弄丢”。
为什么这么重要?因为只要你在入仓第一层就开始混入复杂业务逻辑,后面一旦口径出争议,或者源系统回补历史数据,你就很难再还原“原始数据到底是什么”。这也是为什么现代湖仓里的 Bronze 层同样强调保留原始状态。
DWD:数仓里最重要的一层,通常就是明细层
如果说 ODS 解决的是“先把数据接进来”,那 DWD 解决的是“把数据整理成可以稳定复用的业务事实”。
这一层常见的工作包括:
- 清洗脏数据
- 去重
- 统一字段类型
- 对齐业务事件
- 补充必要维度属性
但这里真正决定质量的,不是 SQL 写了多少行,而是一个词:粒度。
Kimball 方法论里有一个非常核心的原则,叫 grain,也就是粒度声明。翻成大白话就是:
一张事实表,必须先说清楚一行到底代表什么。
还是拿订单主题举例,“订单表”这个说法其实太模糊了。它至少可能对应三种完全不同的事实:
- 一行代表一笔订单
- 一行代表一条订单商品明细
- 一行代表订单某天的状态快照
三种都能叫“订单表”,但分析含义完全不同。
如果你想支撑 SKU 销量、商品销售额、退款金额、店铺成交额这类分析,那 dwd_order_item_detail 更合理的设计通常是:
- 一行代表一条订单商品明细
代表字段可以包括:
order_idorder_item_iduser_idshop_idsku_idorder_statusis_paidis_refundeditem_amountpayable_amountcreate_timepay_time
为什么这样更好?因为它天然适合支持:
- 下单商品件数
- 支付商品件数
- 退款商品件数
- SKU 销售额
- 店铺成交额
如果你把 DWD 设计成“一行一笔订单”,后面一旦做商品级分析,整个链路就会被迫重新拆分,复杂度会迅速上升。
DIM:维度层不是配角,它决定了口径能不能统一
很多团队刚开始搭数仓时,会把维度层看成附属品,觉得先把事实表堆出来更重要。这个顺序短期也许能跑通,长期基本都会付出代价。
维度层的作用,是定义统一的分析上下文。比如:
- 用户是谁
- 商品是什么
- 门店属于哪个区域
- 时间如何定义自然日、自然周、财务月
在电商订单主题下,常见维度表会是:
dim_user:user_id、user_level、register_channel、city_iddim_product:sku_id、spu_id、brand_id、category_iddim_shop:shop_id、shop_type、region_iddim_date:dt、week_no、month_id、is_holiday
一旦维度定义不统一,后面就会出现非常熟悉的一幕:
同样是“新客支付人数”,运营看板一个值,财务看板一个值,活动复盘又是另一个值。问题往往不在 SQL,而在于不同人引用的是不同维度定义、不同时间口径、不同用户标签。
所以,维度层不是可有可无,它是整个数仓里“统一语言”的一部分。
DWS:公共汇总层,解决的是“别重复算”
DWS 的价值,不在于把所有查询都提前跑完,而在于沉淀那些真正高复用、跨场景共享的主题汇总。
在这个电商例子里,比较典型的 DWS 表可能是:
dws_user_order_day- 主键示意:
dt + user_id - 指标示意:
order_cnt、pay_order_cnt、pay_amount
- 主键示意:
dws_sku_sales_day- 主键示意:
dt + sku_id - 指标示意:
sale_qty、sale_amount、refund_amount
- 主键示意:
dws_shop_trade_day- 主键示意:
dt + shop_id - 指标示意:
buyer_cnt、order_cnt、gmv
- 主键示意:
这层真正解决的是两个问题:
- 把公共指标沉淀成资产。
- 让不同消费方不必每次都从 DWD 重新计算。
但 DWS 也是最容易被误用的一层。常见错误包括:
- 还没验证复用价值,就先建一堆汇总表
- 一个业务部门搞一个自己的 DWS 版本
- 指标口径都没稳定,就提前做太深的预聚合
一旦这样做,DWS 很快就会退化成一个“名字听起来很高级的中间层堆场”。
ADS:离业务最近的一层,应该尽量轻
ADS 这一层,主要面向具体业务消费场景,比如:
- 报表
- BI 看板
- 数据接口
- 标签系统
- 推荐或风控应用
在这个例子里,它可能长成这样:
ads_operation_dashboard_day- 指标:支付订单数、GMV、退款金额、客单价、新客支付人数
ads_top_sku_day- 指标:销量、销售额、退款率、转化率
ads_user_value_tag- 结果:会员分层标签、人群经营分层结果
ADS 最大的特点就是:离业务最近,也最容易变。
正因为变化快,它反而不应该承载太多底层公共逻辑。更好的做法是:
- 公共清洗逻辑放在 DWD
- 公共汇总逻辑放在 DWS
- ADS 只做贴近业务场景的最后一跳交付
这样上层需求再怎么变,底层公共资产也不会被拖着一起反复改。
真正决定分层成败的,是这几个边界
把分层做成资产体系,而不是命名体系,关键在于守住边界。
ODS 的边界
- 接收源系统数据
- 保留原始业务语义
- 提供历史回放与审计基础
不适合承载:
- 主题宽表
- 复杂衍生指标
- 面向报表的定制结构
DWD 的边界
- 围绕业务过程沉淀可复用明细事实
- 必须声明粒度
- 同一事实表不得混合多种粒度
DWS 的边界
- 只沉淀高复用、稳定口径的主题汇总
- 汇总口径必须能回溯到明细层
- 不为一次性需求泛化建表
ADS 的边界
- 面向具体业务场景交付数据
- 尽量只做最后一跳组装
- 不重复承载底层公共逻辑
如果这些边界守不住,再标准的分层命名也救不了整体质量。
为什么很多团队的分层最后会失效?
最常见的原因不是技术不行,而是边界塌了。
1. ODS 不再原始
有人为了省事,直接在 ODS 里改业务口径。短期看好像省了几张表,长期却失去了最关键的可追溯性。
2. DWD 粒度混乱
同一个事实表里既有交易明细,又塞进按天汇总字段,最后所有指标都会互相打架。
3. DWS 成了“为了汇总而汇总”
没有稳定复用场景,却提前堆了很多主题汇总。最后没人敢删,也没人敢改。
4. ADS 越层依赖
应用层为了赶需求,直接从 ODS 或杂乱中间表取数,绕开了公共规范。久而久之,整个数仓体系名义上有分层,实际上已经失控。
一个更实用的落地判断方法
如果你正在搭数仓,我不建议一开始先问“我们到底该分几层”,而更建议先问下面六个问题:
- 原始数据是否能回溯?
- 核心业务过程是否有清晰的明细层?
- 粒度是否被明确声明?
- 企业公共维度是否统一?
- 公共指标是否真的沉淀在可复用层?
- 面向业务的结果表是否只做最后一跳?
如果这六件事做对了,哪怕你最终不是标准五层,体系通常也不会太差。反过来,就算名字全叫对了,也可能只是“分层式命名”,不是“分层式设计”。
什么时候适合轻量三层,什么时候适合严格五层?
这通常不是一个“对错问题”,而是一个“阶段问题”。
适合先用轻量三层的情况
可以先压缩成:
- 原始层
- 明细公共层
- 消费层
这更适合:
- 团队很小
- 分析主题不多
- 报表场景有限
- 当前目标是先把第一版链路稳定跑通
适合显式拆成五层的情况
如果你已经出现下面这些信号,就更适合把 DIM 和 DWS 显式拆出来:
- 多个团队同时使用同一批数据
- 指标口径冲突越来越多
- 同一维度定义被反复复制
- 上层查询成本高,明细层被频繁重复扫描
- 看板、接口、标签等消费形态越来越多
简单说就是:
小团队早期可以轻,规模上来以后一定要稳。
最后总结
数据仓库分层,不是一套背诵题,也不是一张好看的架构图。它真正的价值,是通过清晰的职责边界和粒度管理,把数据加工过程变成稳定的数据资产生产线。
无论你叫它 ODS/DWD/DWS/ADS,还是 Bronze/Silver/Gold,本质都在做同一件事:
- 保留原始真相
- 沉淀可复用明细
- 统一公共口径
- 稳定服务业务消费
如果你只能记住一句话,我会建议你记这句:
先保留原始真相,再围绕清晰粒度沉淀公共事实和维度,最后把结果交给业务使用。
这比单纯背下每一层的缩写,更有用。
评论