想象你经营一家连锁餐饮集团,每天几十家门店会送来五花八门的食材:带泥的土豆、未宰杀的鱼、装在不同箱盒里的订单小票……你不会让厨师直接拿这些原料做菜,更不能让每位客人自己到仓库翻找。聪明的做法是建一个中央厨房:收货区原样存放食材,粗加工间洗切分装,预制菜间按标准菜谱做成半成品,前厅再根据顾客点单快速出餐。数据仓库的分层,就是给企业数据搭建这样一条“中央厨房流水线”。ODS 是收货区,DWD 是粗加工间,DWS 是预制菜间,ADS 是前厅出品台。理解了这套流水线,你就理解了数据从原始、明细、汇总到应用的流转逻辑。
对数据工程师、数仓架构师和 AI 从业者而言,掌握分层架构能让你设计出可维护、可复用、口径统一的数据底座,这是训练模型、做分析报表之前最重要的地基工程。
可视化演示
通过ODS-DWD-DWS-ADS四层架构流转、ETL数据加工流水线、命名规范树形结构三个模式,展示数仓分层设计的核心方法论。
本节配有交互动效(进入视口后自动加载,约 3KB)。
ODS-DWD-DWS-ADS四层架构 ODS原始数据层保持原始结构DWD明细层清洗标准化DWS汇总层聚合统计ADS应用层面向业务 分层设计实现数据复用降低重复计算每层职责清晰可维护可追溯 Source → ODS → DWD → DWS → ADS → Application 数据分层是数仓规范化的核心实现数据从原始到应用清晰流转
分层核心理念
一家连锁餐饮集团的中央厨房,绝不会让带泥的土豆直接进炒锅。老板会把整条链路拆成四个站:收货区只管收货,粗加工间负责洗切,预制菜间按菜谱做大锅半成品,前厅再按订单装盘。每站只做一件事,出了问题也只需追责到一站。数据仓库要解决的核心问题,本质上和这个一模一样:企业数据从业务系统里出来,形态芜杂、口径不一,若直接喂给报表和算法,必然一团乱麻。
所以数仓把数据切成分层:ODS 是原始数据的收货区,DWD 是清洗转换后的标准明细,DWS 是按主题汇总的公共指标层,ADS 是面向业务应用的结果层。四层不是四张表,而是一套职责边界。每一层都有明确的输入、输出和质量标准,上游产出什么,下游才能消费什么。
分层带来三个直接收益:口径统一,不管谁做报表都从 DWS 取同一个 GMV;影响隔离,源系统字段变更最多影响 ODS 接入,不会震到 ADS;复用降本,公共汇总只算一次,多份应用共享。代价是多了一层冗余存储和调度,所以也叫“空间换时间”。
就像中央厨房每站都有明确的进出货标准,数据层也通过清晰边界让团队能独立协作。你不需要让每个人都懂全链路,但只要守住层与层之间的接口,流水线就能稳定运转。
- ODS 到 DWD数据从原始层流入明细层,核心动作是清洗和标准化。
- DWD 到 DWS明细数据按主题聚合成公共指标,核心动作是汇总。
- DWS 到 ADS汇总结果按业务应用取出,核心动作是面向需求加工。
- 完整链路四个节点串起来就是数仓的“中央厨房流水线”。
ODS原始层
收货区的工作原则是:先把货收下,别急着改。供应商送来的土豆带着泥,鱼还带着鳞,收货员只清点、贴标签、按到货日期放上货架。如果收货时就把泥洗掉、把鱼切好,万一供应商想对账,你反而说不清原始货长什么样。
ODS 层对应这种“原件仓库”。它从业务库、日志、接口文件中抽取数据,按同步策略落地到数仓。ODS 不负责数据质量,不 join 多张表,更不聚合;它只忠实记录源系统的每一次快照或增量。原始字段叫什么就叫什么,原始值是什么就是什么,哪怕看起来是脏数据也要先留着。
ODS 设计要点包括三块:一是接入方式,小表常用全量快照,大表按日期或主键做增量;二是分区策略,几乎都按 dt 天分区,便于回溯;三是生命周期,原始日志通常保留 30 到 180 天,按成本和合规要求治理。因为 ODS 直接接触源,任何源表结构变化都要在这里先做兼容。
菜收下不等于能直接上桌,但收货区必须原样留底;ODS 也一样,它是整个数仓最接近真相的“证据库”。后续任何清洗逻辑出了问题,都能回到这一层核对原始面目。
- 并集符号 $\bigcup$表示 ODS 由多个来源表汇集而成,不是单一表。
- 源系统 $\text{Source}_i$第 $i$ 个业务源表,比如订单表、用户表、日志表。
- 接入个数 $n$源系统的数量会随着业务扩张而增加。
DWD明细层
粗加工间里,净菜师傅接手带泥土豆,先冲洗、去皮、切块,再按统一规格装进保鲜盒。同样一颗土豆,在中餐和西餐厨房里可以有不同切法,但中央厨房必须提前定好标准:块就是块,丝就是丝,标签上写清楚。
DWD 是数仓里的净菜间。它把 ODS 里杂乱的数据清洗、转换、标准化,结果仍然是明细粒度。所谓明细粒度,就是一行对应一件真实业务事实:一条订单、一次点击、一个用户注册行为。DWD 不做汇总,它保留的是“每一件事实发生了没有、涉及谁、值是多少”。
DWD 常见动作包括:清除空值和脏数据、统一日期与金额格式、把源系统里的代码翻译成统一枚举、做维度退化。维度退化是把常用维度属性直接落进事实表,减少下游 join。比如订单表里直接带上商品类目和门店名称,查询时就不用每次关联维度表。
只有净菜标准统一,预制菜间才敢大批量生产;只有 DWD 标准统一,上层汇总才能放心复用。DWD 是整个数仓里最值得反复打磨的一层,因为它决定了上游一次加工的质量,也决定了千万次查询的体验。
- 维度外键 dim_key指向统一维表的钥匙,例如
user_id、sku_id。 - 属性 attributes随行保存的冗余维度属性,例如商品类目、门店名称。
- 度量 measures可累加的数值事实,例如下单金额、件数。
DWS汇总层
预制菜间不追求菜品多,而追求“大锅出精品”。鱼香肉丝的酱料一次熬 100 人份,分装成料包;门店出餐时不再各自调酱,味道自然一致。这就是公共加工的意义。
DWS 层按主题对 DWD 明细做汇总,生成公共宽表和指标。主题可以是用户、商品、交易、物流等。比如 dws_trade_gmv_d 就是按天、按门店、按商品粒度算出交易总额。它的核心不是展示某一次订单,而是沉淀大家都会用的统计口径。
设计 DWS 时要选对粒度:太小则复用价值低,太大则细节丢失。常用做法是“日加一个业务维度”,例如日门店商品汇总。指标在这个层统一命名、统一计算逻辑,下游 ADS 只取数不重算,这样 GMV 不会出现八种答案。
预制菜一次做一大批,省的是每家门店重复炒制的时间;DWS 把公共指标算一次,省的是每个报表重复扫描明细的成本。这就是数仓“复用”二字最实在的体现。
- $F_{\text{day}}$某一天的事实集合,也就是 DWD 中当天的明细行。
- 求和 $\sum$把每行事实的度量值做累加。
- $v_i$第 $i$ 行事实的贡献值,例如订单金额或下单件数。
ADS应用层
前厅出品台的工作是“看着菜单出菜”。客人点鱼香肉丝,后厨从预制菜间取料包,热锅翻炒、装盘、撒葱花,再端到桌上。同一个预制菜,可以根据门店定位做微调,但不能让每个客人都进后厨自己找料。
ADS 就是数仓的出品台。它面向具体报表、BI、数据产品和接口,按需从 DWS 取汇总指标,必要时再补 DWD 明细,加工成最终的宽表或数据集市。比如 ads_user_purchase_feature 直接供算法取数,里面已经按用户聚合好近 30 天消费特征。
ADS 的特点是“快而专”:可以为了特定查询做一定的冗余,但必须克制。ADS 不该重复建设通用指标,也不该把明细无限堆进来。每张 ADS 表都应该能说清它服务什么场景、由谁维护、数据从哪一层来。
出品台不负责研究菜谱,只负责把已验证的半成品组合成客人要的那盘菜;ADS 不做公共口径研发,只做面向需求的高效输出。层与层之间的分工,在这一站画上句号。
- 选择 $\sigma$按业务条件过滤,例如只看华东区或近 30 天。
- 投影 $\pi$只保留报表需要的指标列,减少输出体积。
- $Q_{\text{report}}$最终给到报表或接口的结果集。
ETL流转
中央厨房里最怕的不是做菜难,而是备餐顺序乱。炖汤要提前 3 小时开火,切配要在开餐前 1 小时完成;传菜梯一次只能上一层,高峰还要排队。没有动线规则,后厨就会撞车。
数仓的 ETL 就是数据动线。ETL 即抽取、转换、加载,实际落地靠调度系统编排无数个任务:ODS 同步任务、DWD 清洗任务、DWS 汇总任务、ADS 生成任务。任务之间必须声明依赖,下游在上游成功后再跑。
流转规范还包含三件事:数据质量监控,在每层入口检查空值率、主键唯一性、行数波动;增量策略,大表避免每天全量重刷;血缘追踪,记录每张表的上游任务和 SQL 来源,出了问题能顺着血缘往回查。
传菜动线理顺了,出餐才稳定;ETL 的依赖和血缘理顺了,数仓数据才能稳定可信地流向每一张报表。没有动线纪律,再好的分层也会被临时任务搅乱。
- 顶点集 $V$表示所有 ETL 任务节点,比如
dwd_order_clean。 - 边集 $E$表示任务之间的依赖关系,$u \to v$ 表示 $v$ 依赖 $u$ 完成。
- 图 $G$整个调度 DAG,不允许出现循环依赖。
命名约定
在中央厨房,标签是纪律。一个冷藏货架上如果只写“食材”,没人知道里面是土豆还是鱼;规范的标签应写成“A区-冷藏-净菜-土豆丝-20250101”。这样任何人都能在 10 秒内找到东西,也知道它属于哪个环节。
数仓表名同样需要“标签格式”。业界通用规则是:层级前缀加主题域加业务过程加周期粒度。例如 dws_trade_gmv_d:dws 表示汇总层,trade 是交易主题,gmv 是业务过程,d 表示日粒度。看到表名就能知道它从哪层来、在讲什么业务、按什么周期更新。
命名约定还包括分区规范,绝大多数表用 dt='YYYY-MM-DD' 分区;维度表用 dim 前缀,事实表用 dwd/dws 前缀,临时表用 tmp 前缀。统一命名让数据地图可读、权限可配、血缘可查。
标签写得清,中央厨房才能高效运转;表名符合规范,数据开发才能不靠口头解释定位一张表。命名不是小事,它是数仓长期可维护性的第一道防线。
- 层级 layer表所在的分层,如
ods、dwd、dws、ads。 - 主题域 domain业务域,如交易
trade、用户user、商品sku。 - 业务过程 business具体动作,如下单
order、支付pay、退款refund。 - 粒度 granularity时间或维度粒度,如
d表示天、m表示月。
建设原则
好的中央厨房,不是设备堆得越多越好,而是每个区域高内聚、低耦合:收货区不要放炒锅,前厅不要设洗菜池。区域的边界一旦模糊,动线就会交叉,效率反而下降。
数仓建设原则可以总结为四句:高内聚低耦合,同一主题的表在 DWS 内聚,不同层之间只通过标准接口流转;分层治理,每层有独立的负责人和验收标准;稳定可控,公共层变更要经过评审和回归测试;持续演进,新需求先复用已有模型,而不是立刻新增一张表。
成本控制是原则里的隐形标尺。DWD 保留明细是必要的存储成本,DWS 冗余宽表是换取查询速度的投资。判断一个新层或新表是否值得,就看它能否提升复用率、降低下游重复计算。一个简单的度量是复用率等于被下游引用的模型数除以总模型数。
中央厨房的管理者每周都会看哪些预制菜被点得多,数仓团队也要定期追踪被引用次数,把重复的“小灶”合并回公共层,让流水线持续变好。建设原则不是写在PPT里的口号,而是每次建模前都要问自己的问题。
- 被复用模型数 $N_{\text{downstream}}$被至少一个下游任务引用的公共模型数量。
- 总模型数 $N_{\text{total}}$数仓中所有可复用层模型的总量。
- 复用率 ReuseRate越高说明公共建设越有效,越低则要警惕重复造表。
AI 应用场景
离线数仓统一指标口径
阶段:数据集成与治理
解决的问题:未分层时,不同部门各自从源表取数,GMV、活跃用户等指标经常口径不一致。分层后,ODS 负责原样接入,DWD 负责清洗,DWS 统一沉淀公共指标,ADS 只面向报表取数,所有消费方共享同一份“预制菜”。
具体案例:某电商平台原本 50 张报表算出 8 个 GMV,业务会议上对不上数。重建四层数仓后,DWS 只有一张 dws_trade_gmv_d,所有报表和 BI 看板都引用这张表,口径从 8 个收敛为 1 个。
机器学习特征工程
阶段:特征工程与数据准备
解决的问题:算法团队要训练用户购买力模型,需要“近 30 天消费金额”等特征。如果每次从原始日志临时计算,慢且不稳定,训练和上线特征还可能不一致。通过 DWD 清洗行为明细、DWS 按用户汇总交易指标,再生成 ADS 特征宽表,可以保证特征口径一致、可复用。
具体案例:某推荐系统团队用 DWS 用户交易宽表叠加 DWD 浏览日志,生成特征表 ads_user_purchase_feature,供模型离线训练和线上打分共同使用,特征延迟从小时级降到分钟级。
Python 演示
# -*- coding: utf-8 -*-from collections import defaultdict class LayeredWarehouse: def __init__(self): self.ods = [] self.dwd = [] self.dws = [] self.ads = [] def load_ods(self, rows): self.ods.extend(rows) def clean_to_dwd(self): self.dwd = [] for row in self.ods: if row.get("amount") is None: continue clean = dict(row) clean["user_id"] = str(clean["user_id"]).strip() clean["amount"] = float(clean["amount"]) self.dwd.append(clean) def aggregate_to_dws(self): grouped = defaultdict(float) for row in self.dwd: grouped[row["date"]] += row["amount"] self.dws = [{"date": d, "gmv": v} for d, v in sorted(grouped.items())] def build_ads(self): self.ads = [r for r in self.dws if r["gmv"] >= 1000] def run(self): self.clean_to_dwd() self.aggregate_to_dws() self.build_ads() if __name__ == "__main__": wh = LayeredWarehouse() wh.load_ods([ {"date": "2025-01-01", "user_id": " u1 ", "amount": 120.0}, {"date": "2025-01-01", "user_id": "u2", "amount": None}, {"date": "2025-01-02", "user_id": "u3", "amount": 1200.0}, ]) wh.run() print(wh.dwd) print(wh.dws) print(wh.ads)
self.ods.extend(rows)把源数据整包放进 ODS,不挑不拣,对应收货区原样收菜。if row.get("amount") is None: continueDWD 清洗时丢弃空值,而不是在 ODS 里动手脚。clean["amount"] = float(clean["amount"])统一金额类型,把字符串或整数转成可计算的浮点数。grouped[row["date"]] += row["amount"]按日期累加,正是 DWS 层最常见的汇总动作。self.ads = [r for r in self.dws if r["gmv"] >= 1000]ADS 按报表阈值过滤,只输出业务需要的结果。wh.run()把 DWD、DWS、ADS 串成一条可重复执行的流水线。
常见误区
误区ODS 就是放数据的临时桶,我顺手把空值和格式都改掉,省得 DWD 再处理一遍。
正解ODS 的目标是“原样落地、可追溯”,不是清洗。若在 ODS 改数据,源系统问题会被抹掉,排查血缘时看不到真实原貌。清洗应放在 DWD,因为 DWD 才负责“净菜”。
为何常见:许多人把 ODS 当作可随意操作的工作区,忽略了它作为原始证据的定位。
误区DWD 也是明细,ODS 也是明细,两层不是重复建设吗?
正解虽然都是明细粒度,但 DWD 是“标准化净菜”:统一字段命名、统一枚举值、消除不一致维度;ODS 是“带泥食材”。二者服务于不同阶段,DWD 让公共加工在下游可以复用。
为何常见:只看到“行数没变”,没有看到字段标准和质量发生的变化。
误区DWS 是给报表用的,我就把明细和汇总全放一张大表,查啥都有。
正解DWS 的核心是公共汇总指标和宽表,不是明细中转站。如果明细进 DWS,表会膨胀、口径难维护,下游还容易绕过 DWD 直接用明细,破坏层次。明细留给 DWD,DWS 只沉淀高频复用的汇总指标。
为何常见:为了暂时查询快,忽略了分层职责。
误区ADS 是应用层,指标越多越显得数仓完善。
正解ADS 面向具体业务场景,按需构建。全量堆指标会造成口径重复、资源浪费,且新需求难以维护。ADS 只放真正被报表或产品使用的指标,像前厅只做菜单上的菜,不做满汉全席。
为何常见:把“完善”理解为“多”,而不是“准”和“快”。
误区我看到别人有 7 层、9 层,我们只有 4 层,是不是太落后了?
正解分层是空间换时间,层数过多会增加调度延迟、存储成本和维护复杂度。ODS、DWD、DWS、ADS 是业界验证过的平衡结构。新增层必须能带来明确的口径或复用收益,否则就是过度设计。
为何常见:盲目追求架构复杂度,忽略成本和收益。
面试题
Q1. 数仓四层架构中,哪个层级负责原样保存来自业务系统的数据?
- A.ODS 原始层← 正确答案
- B.DWD 明细层
- C.DWS 汇总层
- D.ADS 应用层
答案:A。ODS 就像中央厨房收货区,先把供应商送来的食材原样放好,不急着清洗。DWD 才开始洗切,DWS 做预制菜,ADS 负责最终出餐。
Q2. DWD 层相对 ODS 层,最主要的区别是什么?
- A.对数据进行清洗与标准化← 正确答案
- B.只修改一下表的名字
- C.把所有历史数据都删除
- D.直接输出最终报表指标
答案:A。DWD 是粗加工间,负责去空值、统一类型、对齐维度;它不直接出报表,也不删除历史。C 是破坏数据,D 是 ADS 的事。
Q3. DWS 层通常存放哪类数据?
- A.主题化的汇总宽表← 正确答案
- B.原始接口日志文件
- C.前端页面静态资源
- D.数据库临时查询缓存
答案:A。DWS 是预制菜间,把 DWD 的明细按主题聚合成宽表。原始日志归 ODS,静态资源不属于数仓。
Q4. 表名 dws_trade_gmv_d 中,最后的 d 通常代表什么?
- A.按天分区← 正确答案
- B.数据库名
- C.维度表
- D.明细层
答案:A。命名规范常用 d 表示 day 日分区,类似厨房标签上的日期。若是维度表一般写 dim,明细层一般由前缀 dwd 表达。
Q5. 设计 ODS 层时,下列哪项做法是正确的?
- A.保留源系统的原始数据结构← 正确答案
- B.将空值全部替换为 0
- C.把多张表直接合并成宽表
- D.过滤掉所有异常订单
答案:A。ODS 是原件仓库,应保留源系统原貌。空值替换、合并宽表、过滤异常都属于 DWD 清洗或 DWS 汇总的职责,提前做会破坏证据链。
Q6. 数仓分层带来的价值有哪些?
- A.统一指标口径← 正确答案
- B.降低下游开发成本← 正确答案
- C.让每层职责更清晰← 正确答案
- D.让源库结构不再变更
答案:ABC。分层用空间换时间,把公共加工沉淀到公共层,下游不用重复造轮子;也便于血缘追查。但它不能阻止源业务系统改变表结构,只能隔离影响。
Q7. 关于 DWD 层的建设,正确的描述有哪些?
- A.清洗空值和错误格式← 正确答案
- B.统一枚举值与编码← 正确答案
- C.保留明细事实粒度← 正确答案
- D.按天汇总公共指标
答案:ABC。DWD 是净菜间,清洗、标准化、保持明细粒度;按天汇总 GMV 属于 DWS 的聚合动作,不应混入 DWD。
Q8. 数据血缘追踪主要依赖以下哪类信息?
- A.调度依赖和任务日志← 正确答案
- B.报表图表的颜色配置
- C.数据库用户名的长短
- D.服务器 CPU 序列号
答案:A。血缘需要知道每张表由哪些上游任务产出、又被哪些下游任务消费。这些信息来自调度系统、ETL 日志和 SQL 解析,和图表配色、硬件信息无关。
Q9. 在分层数仓中,ADS 层可以直接被哪些对象消费?
- A.报表和 BI 工具← 正确答案
- B.业务分析师查询← 正确答案
- C.下游数据接口← 正确答案
- D.源业务 OLTP 库
答案:ABC。ADS 是前厅出品台,面向报表、分析和接口等消费端。源业务 OLTP 库是上游,不是 ADS 的消费者,ADS 也不应该反向影响业务系统。
Q10. 数据仓库用“空间换时间”来设计分层,以下理解最准确的是?
- A.用冗余存储换取查询与分析提速← 正确答案
- B.用更多计算资源换更少磁盘
- C.用更乱的模型换更快的开发
- D.用删除明细换更小的空间
答案:A。DWS 和 ADS 的宽表会冗余存储部分数据,但公共指标一次算好,下游不用重复扫描明细,查询更快。C 和 D 违背分层原则,B 方向说反了。