导读 / Key Takeaways

  • 数据仓库分层的核心,不是记住 ODS/DWD/DWS/ADS 这些缩写,而是用清晰边界管理数据资产生产过程。
  • ODS 负责保留原始真相,DWD 负责沉淀可复用明细,DWS 负责沉淀公共汇总,ADS 负责面向业务交付结果。
  • 真正决定分层成败的,不是层名,而是原始层是否可追溯、明细层是否粒度清晰、公共层是否口径统一、应用层是否只做最后一跳。
  • 文章会用一个具体的电商订单案例,把源表、维度、明细、汇总和应用层的设计链路完整走一遍。
  • 如果你正在搭数仓,可以先记一句话:先保留原始真相,再围绕清晰粒度沉淀公共事实和维度,最后把结果交给业务使用。

很多团队一聊数据仓库分层,都会顺手写出一串缩写:ODS -> DWD -> DWS -> ADS。看久了,很多人会产生一种错觉,仿佛这只是行业里的命名习惯,背下来就够了。

但真正做过数仓的人通常都知道,问题没这么简单。

一个团队一旦开始接触多业务线、多个报表口径、多个消费场景,数据链路很快就会失控:原始数据和结果表混在一起,指标到处重复算,报表口径彼此对不上,需求一变就得全链路返工。到最后,大家表面上还在说“我们有数仓分层”,实际上系统里只有一堆名字看起来像分层的表。

所以,数据仓库分层的重点,从来都不是记住几个缩写,而是回答一个更本质的问题:

怎样把数据从“原始输入”变成“稳定可复用的数据资产”,而且这个过程还能长期维护下去?

这篇文章就把这件事完整讲清楚。

先说结论:分层不是为了好看,而是为了控制复杂度

数据仓库分层,本质上是在解决四类问题:

  1. 原始数据要可追溯,出了问题能回放。
  2. 明细加工要可复用,不能每个报表都重写一套逻辑。
  3. 公共口径要能收敛,不能同一个指标有三四种算法。
  4. 面向业务的结果要足够稳定,报表、接口、标签系统不能反复扫底层明细。

如果只用一句话概括,分层的意义就是:

把“数据处理过程”变成“可治理的数据资产生产线”。

没有分层时,最常见的症状通常是:

  • 原始数据和结果数据放在一起,责任边界不清
  • 同一指标在多个任务里重复开发
  • 报表之间口径打架,谁都说自己对
  • 上层应用直接依赖底层明细,链路耦合越来越重
  • 源系统修历史数据后,平台无法稳定重算

这些问题表面上看是开发效率问题,本质上其实是边界问题。

传统数仓分层,到底在分什么?

在国内数据仓库实践里,最常见的是这五层:

  • ODS:原始数据接入层
  • DIM:维度层
  • DWD:明细数据层
  • DWS:汇总数据层
  • ADS:应用数据服务层

很多公司虽然实现细节不同,但这套思路基本一致。因为它刚好对应了数据从接入到消费的几种核心职责。

如果换到现代湖仓语境,也可以把它和 Bronze -> Silver -> Gold 做一个工程上的近似理解:

传统数仓 现代湖仓近似对应 主要职责
ODS Bronze 原始接入、保留真相
DWD + 部分 DIM Silver 清洗、校验、标准化、形成可复用明细
DWS + ADS Gold 面向主题分析、聚合、业务消费

这里要强调一点:这不是严格的一一映射,但它们在设计目标上非常接近。传统数仓更强调主题建模和稳定服务层,湖仓更强调统一存储和逐层提纯,本质上仍然是在做同一件事。

为什么很多团队“看起来有分层”,最后还是一团乱?

因为他们分的是名字,不是职责。

真正有效的分层,核心不在于层名对不对,而在于下面这些边界有没有守住:

  • 原始层是不是还保留原始真相
  • 明细层是不是有清晰粒度
  • 公共层是不是沉淀了真正可复用的口径
  • 应用层是不是只做最后一跳交付

只要这些边界塌掉,再标准的 ODS/DWD/DWS/ADS 也会退化成“看起来很规范的中间表堆”。

用一个电商订单案例,把分层一次讲清楚

抽象概念最好理解,但最难真正用起来。要把分层讲透,最好的办法还是看一个完整例子。

假设你现在要为电商业务搭一套订单分析数仓,源系统里有这些表:

  • mysql.order_info:订单主表,包含 order_iduser_idshop_idorder_statuscreate_time
  • mysql.order_item:订单商品明细表,包含 order_item_idorder_idsku_idqtyorigin_amount
  • mysql.payment_info:支付表,包含 pay_idorder_idpay_amountpay_time
  • mysql.refund_info:退款表,包含 refund_idorder_idrefund_amountrefund_status

如果这套链路设计合理,整个分层关系可以长这样:

flowchart LR A["MySQL 源表: order_info / order_item / payment_info / refund_info"] --> B["ODS: ods_order_info / ods_order_item / ods_payment_info / ods_refund_info"] B --> C["DIM: dim_user / dim_shop / dim_sku / dim_date"] B --> D["DWD: dwd_order_item_detail / dwd_payment_detail / dwd_refund_detail"] C --> D D --> E["DWS: dws_user_order_day / dws_sku_sales_day / dws_shop_trade_day"] E --> F["ADS: ads_operation_dashboard_day / ads_top_sku_day / ads_user_value_tag"]

这张图里最关键的不是层数,而是两件事:

  1. 明细事实来自原始层,但会引用统一维度。
  2. 应用层尽量站在公共层之上,而不是绕回去直接扫原始表。

下面我们按层拆开来看。

ODS:先把源数据稳稳接住

ODS 通常是数据进入分析体系后的第一站。它最重要的任务,不是做复杂业务加工,而是保留原始语义、接住历史变化、提供可追溯能力。

在电商订单场景里,ODS 往往会有:

  • ods_order_info
  • ods_order_item
  • ods_payment_info
  • ods_refund_info

ods_order_item 为例,一般至少会保留这些字段:

  • order_id
  • order_item_id
  • sku_id
  • qty
  • origin_amount
  • dt
  • etl_time

你会发现,这一层并不追求“好分析”,而是追求“别把真相弄丢”。

为什么这么重要?因为只要你在入仓第一层就开始混入复杂业务逻辑,后面一旦口径出争议,或者源系统回补历史数据,你就很难再还原“原始数据到底是什么”。这也是为什么现代湖仓里的 Bronze 层同样强调保留原始状态。

DWD:数仓里最重要的一层,通常就是明细层

如果说 ODS 解决的是“先把数据接进来”,那 DWD 解决的是“把数据整理成可以稳定复用的业务事实”。

这一层常见的工作包括:

  • 清洗脏数据
  • 去重
  • 统一字段类型
  • 对齐业务事件
  • 补充必要维度属性

但这里真正决定质量的,不是 SQL 写了多少行,而是一个词:粒度

Kimball 方法论里有一个非常核心的原则,叫 grain,也就是粒度声明。翻成大白话就是:

一张事实表,必须先说清楚一行到底代表什么。

还是拿订单主题举例,“订单表”这个说法其实太模糊了。它至少可能对应三种完全不同的事实:

  • 一行代表一笔订单
  • 一行代表一条订单商品明细
  • 一行代表订单某天的状态快照

三种都能叫“订单表”,但分析含义完全不同。

如果你想支撑 SKU 销量、商品销售额、退款金额、店铺成交额这类分析,那 dwd_order_item_detail 更合理的设计通常是:

  • 一行代表一条订单商品明细

代表字段可以包括:

  • order_id
  • order_item_id
  • user_id
  • shop_id
  • sku_id
  • order_status
  • is_paid
  • is_refunded
  • item_amount
  • payable_amount
  • create_time
  • pay_time

为什么这样更好?因为它天然适合支持:

  • 下单商品件数
  • 支付商品件数
  • 退款商品件数
  • SKU 销售额
  • 店铺成交额

如果你把 DWD 设计成“一行一笔订单”,后面一旦做商品级分析,整个链路就会被迫重新拆分,复杂度会迅速上升。

DIM:维度层不是配角,它决定了口径能不能统一

很多团队刚开始搭数仓时,会把维度层看成附属品,觉得先把事实表堆出来更重要。这个顺序短期也许能跑通,长期基本都会付出代价。

维度层的作用,是定义统一的分析上下文。比如:

  • 用户是谁
  • 商品是什么
  • 门店属于哪个区域
  • 时间如何定义自然日、自然周、财务月

在电商订单主题下,常见维度表会是:

  • dim_useruser_iduser_levelregister_channelcity_id
  • dim_productsku_idspu_idbrand_idcategory_id
  • dim_shopshop_idshop_typeregion_id
  • dim_datedtweek_nomonth_idis_holiday

一旦维度定义不统一,后面就会出现非常熟悉的一幕:

同样是“新客支付人数”,运营看板一个值,财务看板一个值,活动复盘又是另一个值。问题往往不在 SQL,而在于不同人引用的是不同维度定义、不同时间口径、不同用户标签。

所以,维度层不是可有可无,它是整个数仓里“统一语言”的一部分。

DWS:公共汇总层,解决的是“别重复算”

DWS 的价值,不在于把所有查询都提前跑完,而在于沉淀那些真正高复用、跨场景共享的主题汇总。

在这个电商例子里,比较典型的 DWS 表可能是:

  • dws_user_order_day
    • 主键示意:dt + user_id
    • 指标示意:order_cntpay_order_cntpay_amount
  • dws_sku_sales_day
    • 主键示意:dt + sku_id
    • 指标示意:sale_qtysale_amountrefund_amount
  • dws_shop_trade_day
    • 主键示意:dt + shop_id
    • 指标示意:buyer_cntorder_cntgmv

这层真正解决的是两个问题:

  1. 把公共指标沉淀成资产。
  2. 让不同消费方不必每次都从 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 或杂乱中间表取数,绕开了公共规范。久而久之,整个数仓体系名义上有分层,实际上已经失控。

一个更实用的落地判断方法

如果你正在搭数仓,我不建议一开始先问“我们到底该分几层”,而更建议先问下面六个问题:

  1. 原始数据是否能回溯?
  2. 核心业务过程是否有清晰的明细层?
  3. 粒度是否被明确声明?
  4. 企业公共维度是否统一?
  5. 公共指标是否真的沉淀在可复用层?
  6. 面向业务的结果表是否只做最后一跳?

如果这六件事做对了,哪怕你最终不是标准五层,体系通常也不会太差。反过来,就算名字全叫对了,也可能只是“分层式命名”,不是“分层式设计”。

什么时候适合轻量三层,什么时候适合严格五层?

这通常不是一个“对错问题”,而是一个“阶段问题”。

适合先用轻量三层的情况

可以先压缩成:

  • 原始层
  • 明细公共层
  • 消费层

这更适合:

  • 团队很小
  • 分析主题不多
  • 报表场景有限
  • 当前目标是先把第一版链路稳定跑通

适合显式拆成五层的情况

如果你已经出现下面这些信号,就更适合把 DIMDWS 显式拆出来:

  • 多个团队同时使用同一批数据
  • 指标口径冲突越来越多
  • 同一维度定义被反复复制
  • 上层查询成本高,明细层被频繁重复扫描
  • 看板、接口、标签等消费形态越来越多

简单说就是:

小团队早期可以轻,规模上来以后一定要稳。

最后总结

数据仓库分层,不是一套背诵题,也不是一张好看的架构图。它真正的价值,是通过清晰的职责边界和粒度管理,把数据加工过程变成稳定的数据资产生产线。

无论你叫它 ODS/DWD/DWS/ADS,还是 Bronze/Silver/Gold,本质都在做同一件事:

  • 保留原始真相
  • 沉淀可复用明细
  • 统一公共口径
  • 稳定服务业务消费

如果你只能记住一句话,我会建议你记这句:

先保留原始真相,再围绕清晰粒度沉淀公共事实和维度,最后把结果交给业务使用。

这比单纯背下每一层的缩写,更有用。