☰
数据分层实战:ODS、DW、DM三层架构设计与落地指南
2026/10/16 5:52:15 网站建设 项目流程

1. 数据分层到底在分什么:从一堆乱表到一套可维护的数据体系

刚入行那会儿,我最怕听到的一句话就是“帮我从数仓里取个数”。因为那时候所谓的“数仓”,其实就是几百张命名毫无规律的宽表堆在一起,有的叫tmp_20230101_final,有的叫user_info_new_v2,还有的叫aaa_test_不要删。每次取数都像考古,得先问三四个同事这张表到底能不能用、字段有没有坑、口径是不是最新的。后来团队痛定思痛做了一次彻底的数据分层改造,把 ODS、DW、DM 这三层体系搭起来,取数效率直接翻了好几倍,口径扯皮的事也少了一大半。

数据分层这件事,说白了就是给数据建一栋有楼层、有房间号的楼。ODS 是一楼仓库,原封不动地接收从业务系统搬过来的原始货物;DW 是二楼的加工车间,把原材料清洗、拆分、组装成标准件;DM 是三楼的展厅,按不同客户的需求把标准件摆成一个个可以直接拿走用的成品。没有分层的时候,所有东西堆在一个大平层里,找东西靠翻,改东西怕碰倒别的,时间一长必然失控。

这套体系能解决的核心问题有三个:第一是口径统一,同一个“活跃用户”的定义只在一个地方维护,不会出现三个部门三个算法;第二是血缘清晰,任何一个指标出问题,都能顺着层级往上追溯,快速定位是哪一步算错了;第三是成本可控,原始数据只存一份,中间层可以按需保留,应用层可以针对性地做聚合和缓存,存储和计算资源不会无脑膨胀。

适合看这篇内容的人,我大致分三类:一是刚接触数仓、被 ODS/DW/DM 这些缩写搞得有点懵的新人;二是正在推动团队做数据治理、需要一套可落地分层方案的负责人;三是日常要写 SQL 取数、但总觉得表太多太乱、想搞清楚背后组织逻辑的分析师。不管你用的是哪种技术栈,分层的思路是通用的,理解了这套逻辑,换任何工具都能快速上手。

2. 三层架构的设计逻辑与选型考量

2.1 为什么是 ODS、DW、DM 这三层,而不是两层或五层

很多人会问,分层到底分几层合适。我见过只分两层的团队,也见过硬生生拆出六七层的团队。两层的问题是 ODS 直接对接应用,所有清洗逻辑都堆在应用层,改一个口径要动十几张报表;六七层的问题是链路太长,数据从源头到应用要经过五六次落表,延迟高、排查难、维护成本爆炸。三层是一个比较舒服的平衡点:ODS 负责“接进来”,DW 负责“洗干净并标准化”,DM 负责“按场景组装好”。

ODS 层的核心原则是贴源。业务系统里是什么样,ODS 里就尽量保持什么样,不做业务逻辑的加工,只做必要的技术处理,比如字段类型统一、敏感信息脱敏、增量抽取的时间戳标记。这样做的好处是,一旦上游数据出问题,ODS 可以作为“原始证据”来核对,不会因为中间做了加工而说不清楚。

DW 层是真正体现数据建模能力的地方。这一层通常又会细分为 DWD(明细数据层)和 DWS(汇总数据层)。DWD 做的是清洗、去重、维度补全、码值转换这些“标准化”工作,把不同业务系统里五花八门的表达统一成一套标准。DWS 则是在 DWD 的基础上做轻度聚合,比如按天、按用户、按品类做汇总,为上层应用准备好“半成品”。

DM 层是面向应用的。不同部门、不同场景对数据的需求差异很大,DM 层就是把这些差异消化掉,给每个场景提供一张或几张“开箱即用”的表。比如营销部门要看用户画像,风控部门要看交易异常,运营部门要看日活留存,这些需求在 DM 层各自有对应的表,互不干扰。

2.2 分层带来的核心收益与常见误区

分层最直接的收益是复用。没有分层的时候,每个报表都从原始表开始写,同样的清洗逻辑重复几十遍,改一处漏一处。有了 DW 层之后,清洗逻辑只写一次,所有下游共享,改口径只需要改一个地方。我粗略算过,我们团队做完分层之后,新增一张报表的开发时间从平均两天缩短到半天,大部分时间花在确认需求而不是写 SQL 上。

第二个收益是可追溯。数据出问题的时候,排查路径非常清晰:先看 DM 层的指标对不对,如果不对就看 DW 层的明细,再不对就看 ODS 层的原始数据。每一层都有明确的输入和输出,问题定位从“大海捞针”变成“按图索骥”。

但分层也有常见的误区。第一个误区是为了分层而分层,把本来简单的链路硬拆成很多层,结果每层的数据量都很小,维护成本却很高。第二个误区是ODS 层做太多加工,比如在 ODS 里就把一些字段清洗了,导致后面想追溯原始值时发现已经被改过了。第三个误区是DM 层直接读 ODS,跳过了 DW 层,短期看省事,长期看就是给自己挖坑,因为一旦 ODS 结构变化,所有 DM 表都要跟着改。

提示:分层的层数不是固定的,三层是通用起点。如果业务简单、数据量小,ODS 加 DM 两层也能跑;如果业务复杂、合规要求高,在 DW 里再拆出 DWD 和 DWS 也很常见。关键是每一层要有明确的职责边界,不能互相串。

2.3 不同技术栈下的分层落地差异

虽然分层的逻辑是通用的,但不同技术栈在落地时会有一些差异。传统关系型数据库做数仓的时候,ODS 通常是一张张跟源表结构一致的表,DW 用视图或者物化视图来实现,DM 用宽表或者聚合表。这种方案的好处是 SQL 兼容性好,坏处是数据量大了之后性能会吃力。

大数据平台上的分层就更灵活一些。ODS 一般用 Parquet 或者 ORC 格式存在分布式文件系统上,按天分区。DW 层用 Hive 或者 Spark SQL 来做清洗和聚合,DWD 保持明细粒度,DWS 做轻度汇总。DM 层可以同步到关系型数据库或者分析型数据库里,供报表和接口直接查询。这种方案能扛住很大的数据量,但链路更长,对调度和监控的要求也更高。

还有一些团队用数据湖的方案,ODS 和 DW 都放在数据湖里,用统一的元数据管理来维护血缘。这种方案灵活度高,但如果没有好的治理机制,很容易退化成“数据沼泽”。我的经验是,不管用什么技术栈,元数据管理和数据质量监控都是分层能不能落地的关键。没有这两样,分层就只是把表换了个地方放而已。

3. 核心细节拆解:每一层到底该怎么建表

3.1 ODS 层建表规范与抽取策略

ODS 层的建表,我的原则是“结构对齐、命名可读、分区合理”。结构对齐是指字段名和类型尽量跟源系统保持一致,不要自作主张改名字,否则后面核对数据的时候会很痛苦。命名可读是指表名要能看出是哪张源表、什么抽取频率,比如ods_order_info_di表示订单信息表的日增量,ods_user_profile_df表示用户画像表的日全量。分区合理是指按时间分区,通常是按天,这样增量抽取和回溯都很方便。

抽取策略上,全量和增量要分开处理。全量表每天覆盖一次,适合数据量不大、变化频繁的维度表;增量表按时间戳或者自增 ID 抽取,适合数据量大、只追加的事实表。增量抽取的关键是幂等,也就是说同一天的数据重复跑多次,结果应该是一样的。实现方式通常是在抽取前先删除目标分区,再重新写入,或者用 merge 的方式按主键更新。

-- ODS 层增量抽取示例(伪代码,展示逻辑) INSERT OVERWRITE TABLE ods_order_info_di PARTITION (dt = '${bizdate}') SELECT order_id, user_id, order_amount, order_status, create_time, update_time FROM source_order_info WHERE update_time >= '${bizdate} 00:00:00' AND update_time < '${bizdate} 23:59:59';

这段 SQL 里,INSERT OVERWRITE保证了幂等,PARTITION按业务日期分区,WHERE条件用update_time来圈定当天变化的数据。注意这里用的是update_time而不是create_time,因为订单状态可能会变,用创建时间会漏掉更新。

注意:ODS 层不要做业务逻辑的清洗,比如不要把“男/女”转换成“1/0”,不要把金额单位从分转成元。这些转换放在 DW 层做,ODS 保持原始面貌,方便核对。

3.2 DW 层建模:维度建模与范式建模的取舍

DW 层是分层的核心,也是建模工作量最大的地方。这里绕不开一个经典问题:用维度建模还是范式建模。我的经验是,DWD 层用维度建模的思路,DWS 层用轻度聚合的思路,两者结合,既保证灵活性又保证性能。

维度建模的核心是事实表和维度表。事实表存业务过程,比如订单、支付、点击,每条记录代表一次业务事件。维度表存描述信息,比如用户、商品、门店,每条记录代表一个实体。事实表通过外键关联维度表,查询的时候做 join 就能拿到完整的业务视图。这种建模方式的优点是直观、查询友好,缺点是维度变化的时候处理起来麻烦,需要用到缓慢变化维的技术。

范式建模则是把数据拆得很细,消除冗余,保证一致性。这种方式的优点是存储省、更新快,缺点是查询的时候要 join 很多表,对使用者的要求高。在数仓场景下,纯范式建模很少见,通常是维度建模为主、范式建模为辅,在 DWD 层做一些适度的规范化。

-- DWD 层订单明细表示例 CREATE TABLE dwd_order_detail_di ( order_id STRING COMMENT '订单ID', user_id STRING COMMENT '用户ID', product_id STRING COMMENT '商品ID', category_id STRING COMMENT '品类ID', order_amount DECIMAL(18,2) COMMENT '订单金额', discount_amount DECIMAL(18,2) COMMENT '优惠金额', pay_amount DECIMAL(18,2) COMMENT '实付金额', order_status STRING COMMENT '订单状态', create_time STRING COMMENT '创建时间', pay_time STRING COMMENT '支付时间' ) PARTITIONED BY (dt STRING COMMENT '业务日期') STORED AS ORC;

这张表里,order_status已经做了码值转换,把源系统的各种状态统一成了标准值。pay_amount是计算字段,等于order_amount减去discount_amount。这些加工都在 DWD 层完成,下游直接用就行。

3.3 DM 层面向应用的组装策略

DM 层的设计思路跟 DW 层完全不同。DW 层追求的是通用和规范,DM 层追求的是好用和快。同一个指标,在不同场景下可能有不同的维度和粒度要求,DM 层就是把这些要求消化掉,给每个场景提供最合适的表。

举个例子,运营部门要看日活和留存,那 DM 层就建一张dm_user_active_di,按天存活跃用户数和次日、七日留存率。营销部门要看用户分群,那就建一张dm_user_segment_df,按用户标签存分群结果。风控部门要看异常交易,那就建一张dm_trade_risk_di,按交易存风险评分和规则命中情况。

DM 层的表通常会有一些冗余,这是故意的。比如用户分群表里可能会冗余用户的年龄、性别、城市这些维度,这样营销部门用的时候不用再 join 用户表。冗余带来的存储成本,跟查询性能的提升比起来,通常是值得的。

提示:DM 层的表要控制数量,不能每个报表都建一张。我的做法是按主题域来组织,比如用户主题、交易主题、商品主题,每个主题下建几张核心表,覆盖大部分场景。特殊需求用视图或者临时表来解决,不要轻易往 DM 层加表。

4. 实操过程:从零搭建一套分层体系的完整步骤

4.1 第一步:梳理数据源和业务过程

动手建表之前,先要把数据源摸清楚。我通常会做一张清单,列出所有业务系统、每个系统里的核心表、表的更新频率、数据量级、有没有主键、有没有时间戳字段。这张清单是后续所有工作的基础,宁可多花两天时间梳理,也不要急着动手建表。

梳理业务过程的时候,我会跟业务方聊,问他们“你们最关心哪些事情”。比如电商业务,核心过程就是浏览、加购、下单、支付、退款。每个过程对应一张事实表,过程的度量值就是事实表的字段,过程的描述信息就是维度表的字段。这个过程不需要很精确,先有个大概的框架,后面再逐步完善。

4.2 第二步:设计分层结构和命名规范

结构和规范要在动手之前定好,否则后面改起来很痛苦。我的命名规范大致是这样的:ODS 层用ods_前缀,DW 层用dwd_和dws_前缀,DM 层用dm_前缀。表名用英文小写加下划线,能表达业务含义,不要用拼音缩写。分区字段统一用dt,格式是yyyyMMdd。

分层结构上,ODS 层按源系统分库,DW 层按主题域分库,DM 层按应用场景分库。比如ods_ecommerce、dwd_trade、dws_user、dm_operation。这样组织的好处是,找表的时候先定位到库,再定位到表,路径很清晰。

# 目录结构示例(以 Hive 为例) /user/hive/warehouse/ ├── ods_ecommerce.db/ │ ├── ods_order_info_di/ │ ├── ods_user_info_df/ │ └── ods_product_info_df/ ├── dwd_trade.db/ │ ├── dwd_order_detail_di/ │ └── dwd_payment_detail_di/ ├── dws_user.db/ │ └── dws_user_action_1d/ └── dm_operation.db/ ├── dm_user_active_di/ └── dm_user_retention_di/

4.3 第三步:逐层实现并验证数据一致性

建表之后,要逐层验证数据。我的做法是,每建完一层,就跑几个核对 SQL,确认数据量和关键指标跟上一层对得上。比如 ODS 层抽了 100 万条订单,DWD 层清洗之后应该还是 100 万条(除非有去重逻辑),DWS 层按天汇总之后,总金额应该跟 DWD 层一致。

核对的时候要特别注意边界情况。比如跨天更新的订单,在 ODS 层可能出现在两个分区里,DWD 层去重之后只保留一条,这时候数据量会对不上,需要确认去重逻辑是否正确。还有空值处理,源系统里某些字段可能为空,DWD 层是保留空值还是给默认值,这个要跟业务方确认清楚。

-- 数据一致性核对示例 -- 核对 DWD 层和 ODS 层的订单金额是否一致 SELECT 'dwd' AS layer, dt, COUNT(*) AS order_cnt, SUM(pay_amount) AS total_amount FROM dwd_order_detail_di WHERE dt = '20240101' GROUP BY dt UNION ALL SELECT 'ods' AS layer, dt, COUNT(*) AS order_cnt, SUM(order_amount - discount_amount) AS total_amount FROM ods_order_info_di WHERE dt = '20240101' GROUP BY dt;

这个核对 SQL 跑出来,如果两层的order_cnt和total_amount一致,说明清洗逻辑没问题。如果不一致,就要去查是哪一步出了问题。

4.4 第四步:配置调度和监控

分层体系建好之后,要靠调度来驱动。通常的依赖关系是 ODS 先跑,跑完触发 DW,DW 跑完触发 DM。调度工具可以用开源的,也可以用云上的服务,关键是依赖配置要准确,不能出现上游没跑完下游就开始跑的情况。

监控要覆盖三个方面:数据量监控,如果某天数据量突然比平时少很多或者多很多,要告警;数据质量监控,比如主键重复率、空值率、枚举值分布,超过阈值要告警;任务时效监控,如果某个任务跑得比平时慢很多,要提前发现,避免影响下游。

注意:调度依赖不要配得太死,比如 ODS 有十张表,DW 只依赖其中五张,那就只配这五张的依赖,不要等所有 ODS 表都跑完才跑 DW。这样能缩短整体链路的时间,也能避免因为一张不相关的表延迟导致整个链路卡住。

5. 常见问题与排查技巧实录

5.1 数据量对不上:从分区到主键的排查路径

数据量对不上是最常见的问题,排查路径我总结了一个顺序:先看分区,再看主键,最后看过滤条件。分区问题通常是上游写了多个分区,下游只读了一个分区,或者分区格式不一致导致读不到数据。主键问题通常是去重逻辑写错了,比如用了错误的字段做 distinct,导致数据被误删。过滤条件问题通常是 where 条件写得太严,把一些边界数据过滤掉了。

我遇到过一个典型案例:ODS 层某天有 100 万条记录,DWD 层只有 80 万条。查了半天发现是 DWD 层的 SQL 里有一个WHERE user_id IS NOT NULL,而源数据里有 20 万条记录的user_id是空的。这些空值记录其实是游客行为,业务上是有效的,不应该被过滤掉。后来把条件改成WHERE user_id IS NOT NULL OR device_id IS NOT NULL,数据量就对上了。

5.2 口径不一致:同一个指标在不同层算出不同结果

口径不一致的问题,根源通常是计算逻辑没有统一下沉。比如“活跃用户”这个指标,ODS 层可能按登录算,DWD 层可能按有行为算,DM 层可能按有交易算,三个层算出来三个数。解决的办法是,把指标的定义和计算逻辑统一放在 DW 层,DM 层只做引用,不做重新计算。

具体操作上,我会在 DWS 层建一张“指标中间表”,把常用指标的计算逻辑固化下来。比如dws_user_active_1d这张表,每天算好活跃用户数、新增用户数、留存用户数,DM 层直接用这些字段,不再自己算。这样口径就统一了,改逻辑也只需要改一个地方。

问题现象可能原因排查方法解决方案
数据量突然减半上游分区缺失或抽取条件变更检查 ODS 分区和抽取 SQL补数据并修复抽取逻辑
指标数值偏高重复计算或 join 膨胀检查 join 条件和去重逻辑调整 join 方式或加 distinct
指标数值偏低过滤条件过严或空值处理不当检查 where 条件和空值分布放宽条件或补默认值
任务延迟数据倾斜或资源不足查看任务执行计划和资源队列优化 SQL 或调整资源配比

5.3 性能瓶颈:分层之后查询反而变慢了怎么办

分层之后查询变慢,通常是因为链路太长或者中间层数据量太大。链路长的问题,可以通过在 DM 层做预聚合来解决,把常用的聚合结果提前算好,查询的时候直接读结果。中间层数据量大的问题,可以通过分区裁剪、列裁剪、谓词下推这些优化手段来缓解。

我自己的经验是,DWS 层的表要控制好粒度。如果粒度太细,比如按用户按小时汇总,数据量可能比 DWD 层还大,查询的时候反而更慢。粒度太粗,比如按月汇总,又满足不了按天查询的需求。通常按天、按用户、按核心维度汇总是一个比较合适的粒度,能覆盖大部分场景。

还有一个容易被忽略的点是小文件问题。分层之后,每层每天都会产生一批文件,如果分区太多、文件太小,查询的时候打开文件的开销会很大。解决的办法是定期做小文件合并,或者在写入的时候控制 reduce 的数量。这个在数据量大的时候特别明显,我见过一个任务因为小文件太多,查询时间从几秒变成了几分钟。

5.4 避坑清单:分层改造中容易踩的五个坑

第一个坑是ODS 层做清洗。前面说过,ODS 要保持原始,清洗放 DW。我见过团队在 ODS 层就把手机号脱敏了,结果后面要做用户画像的时候发现原始手机号没了,没法做关联。

第二个坑是DM 层直接读 ODS。短期看省事,长期看是灾难。一旦 ODS 结构变化,所有 DM 表都要改,而且绕过了 DW 层的清洗逻辑,数据质量没法保证。

第三个坑是命名不规范。表名用拼音、用缩写、用日期后缀,时间一长没人记得住。命名规范要在项目开始就定好,并且严格执行。

第四个坑是没有数据质量监控。分层之后链路变长,问题发现得晚,修复成本高。监控要在每一层都配上,尤其是 ODS 到 DW 这一层,是数据质量的关键卡点。

第五个坑是过度设计。不是所有数据都要分三层,有些临时数据、日志数据,直接放 ODS 加一个简单的 DM 就够了。分层是为了解决问题,不是为了好看。

6. 分层体系的扩展方向与个人实践体会

分层体系建好之后,不是就一劳永逸了。业务在变,数据在变,分层体系也要跟着演进。我自己的做法是,每半年做一次复盘,看看哪些表没人用了可以下线,哪些层的职责边界模糊了需要调整,哪些新的数据源需要纳入体系。下线没人用的表特别重要,我见过太多团队只建表不删表,数仓里躺着几千张没人用的表,维护成本高得吓人。

扩展方向上,有两个趋势值得关注。一个是数据湖和数仓的融合,ODS 和 DW 的边界在变得模糊,很多团队用数据湖来存原始数据和明细数据,用数仓来做聚合和应用,两者通过统一的元数据打通。另一个是实时分层,传统的分层是按天调度的,现在越来越多的场景需要实时数据,ODS 用消息队列接,DW 用流计算做,DM 用分析型数据库提供查询,整套链路是实时的。实时分层的复杂度比离线高很多,建议先把离线分层做扎实,再考虑实时。

我个人在实际操作中的体会是,分层这件事,技术只占三成,管理和规范占七成。工具再好,如果团队不遵守命名规范、不维护元数据、不做数据质量监控,分层体系很快就会退化成另一个“乱表堆”。反过来,哪怕工具简陋一点,只要规范执行到位,分层体系也能跑得很好。所以如果你正在推动分层改造,先把规范和流程定下来,再选工具,顺序不要反了。

最后分享一个小技巧:建分层体系的时候,可以先从一两个核心业务过程开始,跑通 ODS 到 DM 的完整链路,验证没问题之后再逐步扩展到其他业务过程。不要一上来就全面铺开,那样风险太大,一旦某个环节出问题,整个项目都可能被叫停。小步快跑,逐步迭代,是我试过最稳的推进方式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询