1. 从一次数据口径打架说起:为什么分层不是画着玩的
刚入行那会儿,我参与过一个零售行业的分析项目。业务方早上开会说"昨天华东区的销售额是 320 万",到了下午财务那边拉出来的数字却是 287 万。两边都觉得自己没错,吵到数据团队这里,我们一查,发现业务方看的是订单创建时间,财务用的是支付完成时间,而且业务方那张表里还混着测试环境的脏数据。这件事让我第一次真切意识到:如果数据从源头到消费端没有清晰的分层和职责边界,后面所有的分析、报表、决策都是建在流沙上的。
数据仓库的分层结构,说白了就是给数据从"原始状态"到"可放心使用状态"设计一条有明确站点的流水线。它要解决的核心问题有三个:第一,让不同来源、不同格式的数据有一个统一的归集和清洗路径;第二,让每一层的数据职责单一,避免"一张表打天下"导致的逻辑混乱;第三,让下游用数的人能快速找到自己该看哪一层,而不是每次都去翻原始日志。
这套东西适合谁看?如果你是刚接触数仓的数据开发、分析师,或者是从业务转数据岗、需要理解"为什么不能直接查原始表"的同学,这篇内容会帮你把分层的来龙去脉、每层到底干什么、实际落地时怎么设计讲清楚。我不会只给你一个 ODS-DWD-DWS-ADS 的缩写列表,而是把每一层背后的取舍逻辑、踩过的坑、以及不同规模团队该怎么裁剪方案都摊开说。
先给一个最朴素的认知:分层不是为了显得架构高级,而是为了控制复杂度和降低重复计算。一个没有分层的数仓,就像把所有食材、半成品、成品全堆在一个灶台上,看着热闹,真做起饭来手忙脚乱。分层就是给你配了备菜区、烹饪区、出餐区,各司其职。
2. 四层主干结构:每一层到底在解决什么问题
2.1 ODS 层:贴着源系统走,别急着做业务判断
ODS(Operational Data Store,操作数据存储层)是整个数仓的地基。它的核心原则只有一条:尽可能原样保留源系统的数据,只做最轻量的技术处理。什么叫最轻量?比如字段类型统一、时间格式规整、加上数据抽取的时间戳和来源标识,这些可以做。但像"把订单状态 1 翻译成'已支付'"这种带业务含义的转换,坚决不放在这一层。
为什么这么强调"原样"?因为一旦你在 ODS 层做了业务逻辑,后面如果业务规则变了,你连回溯原始数据的机会都没有。我见过一个团队在 ODS 层就把退款订单直接过滤掉了,结果半年后业务要看退款率趋势,发现原始数据里根本没有退款记录,只能重新从源库抽,白白浪费了大量存储和计算资源。
ODS 层通常按天分区,采用增量抽取或全量抽取。增量抽取适合有明确更新时间戳的表,全量抽取适合数据量小但变更频繁的维表。这里有个实操细节:增量抽取一定要处理"迟到数据"。比如源系统里一条订单的更新时间是昨天,但因为同步延迟今天才到,如果你的抽取条件是"更新时间等于今天",这条数据就永远丢了。稳妥的做法是每次抽取最近 N 天的数据做覆盖写入,N 一般取 3 到 7 天。
提示:ODS 层建议保留至少 6 个月到 1 年的历史数据,具体看存储成本和合规要求。别为了省空间把原始数据删了,后面想补都补不回来。
2.2 DWD 层:清洗、规范化、拉链,把数据变成"可信原料"
DWD(Data Warehouse Detail,明细数据层)是数仓里最考验功力的地方。它要做的事情是把 ODS 层那些"毛坯"数据,加工成干净、规范、可复用的明细数据。具体包括:去重、空值处理、字段标准化(比如把"男/女/M/F"统一成"male/female")、维度退化(把常用的维度字段直接冗余进事实表,减少关联)、以及缓慢变化维的处理。
缓慢变化维是 DWD 层的重头戏。举个典型场景:一个客户的所属区域从"华东"变成了"华南",你希望历史订单仍然关联到当时的"华东",还是统一显示成现在的"华南"?前者需要拉链表,后者直接覆盖就行。拉链表的设计思路是给每条记录加上start_date和end_date,当前有效记录的end_date设为一个极大值(比如 9999-12-31)。这样查询某个时间点的状态时,用where dt between start_date and end_date就能精准还原。
DWD 层还有一个容易被忽视的职责:统一命名规范和度量单位。比如有的源表金额单位是"分",有的是"元",到了 DWD 层必须统一成"元"。有的源表用order_id,有的用order_no,DWD 层要统一成同一个字段名。这些看似琐碎的工作,恰恰是下游能高效使用的关键。
2.3 DWS 层:按主题聚合,让常用指标"一次算好,处处能用"
DWS(Data Warehouse Summary,汇总数据层)的核心思想是预聚合。你想想,如果每个报表都去 DWD 层扫全量明细,那计算资源会被重复消耗无数次。DWS 层就是把这些高频的聚合逻辑提前算好,按主题域(比如交易、用户、商品、流量)组织成宽表。
举个例子,交易主题下可以有一张"用户日粒度交易汇总表",包含每个用户每天的订单数、支付金额、退款金额、首单时间等。这张表按user_id + dt聚合,下游做用户画像、复购分析时直接查这张表就行,不用再去关联订单明细和支付明细。
DWS 层的设计有个关键取舍:聚合粒度越细,灵活性越高,但存储和计算成本也越大。粒度太粗(比如只按天汇总),遇到"按小时看趋势"的需求就抓瞎;粒度太细(比如按秒),又失去了预聚合的意义。我的经验是,大部分场景按"天 + 业务主键"的粒度就够了,个别实时性要求高的场景再单独走实时链路。
2.4 ADS 层:面向应用,怎么好用怎么来
ADS(Application Data Store,应用数据层)是直接对接报表、大屏、接口的层。这一层不再追求通用性,而是怎么方便应用怎么来。比如某个报表需要"华东区 Top 10 门店的周销售额环比",那 ADS 层就可以直接建一张专门服务这个报表的表,字段名甚至可以用中文拼音缩写,只要应用方看得懂就行。
ADS 层的数据通常会有较强的时效性要求,很多团队会在这里引入 ClickHouse、Doris 这类 OLAP 引擎来加速查询。但要注意,ADS 层不是"垃圾桶",不能什么临时需求都往这里塞。我的做法是:只有被两个以上应用复用的指标,才值得在 ADS 层单独建表,一次性的分析需求直接在 DWS 层查就行。
3. 分层之外的那些层:ODS 之前和 ADS 之后还有什么
3.1 数据源层与贴源缓冲:别让抽取任务压垮业务库
严格来说,数据源层(比如业务系统的 MySQL、日志文件、第三方接口)不算数仓的一部分,但它和 ODS 层之间的衔接方式直接影响数仓的稳定性。我踩过最大的坑就是:抽取任务直接连业务库做全表扫描,结果把业务库的 CPU 打满了,线上订单都卡了。
后来我们加了一个"贴源缓冲"的环节:先用 Canal 或者 Flink CDC 把业务库的变更日志实时同步到一个中间库,ODS 层再从中间库抽取。这样业务库的压力就小多了。如果团队没有实时同步的条件,至少也要做到:抽取任务在业务低峰期执行、加限流、避免select *。
3.2 维表层:别把维度散落在各个事实表里
维度表(Dim 表)严格来说可以放在 DWD 层,但因为它太重要了,很多团队会单独拎出来管理。维度表的核心是一致性:同一个"门店"在交易事实表里叫store_id,在流量事实表里叫shop_id,那下游关联时就会疯掉。所以维表要统一主键、统一命名、统一口径。
维度表还分稳定维和变化维。稳定维比如"省份城市"这种基本不变的,直接全量覆盖就行;变化维比如"客户等级"这种会变的,就要用拉链表或者快照表来处理。我一般建议:变化频率低于每月一次的,用拉链;高于每月一次的,用每日快照,因为拉链表的关联查询在数据量大时性能会明显下降。
3.3 数据集市层:给不同业务线开小灶
数据集市(Data Mart)可以理解为 ADS 层的一个变体,它是面向特定业务线(比如市场部、供应链部)的独立数据集。和 ADS 的区别在于,数据集市通常有更强的业务属性,甚至会有自己的主题划分和指标体系。
什么时候需要数据集市?当公司业务线多、各业务线指标口径差异大、且数据团队人力充足时,可以考虑建。但小团队千万别一上来就搞数据集市,先把 ODS-DWD-DWS-ADS 这条主干跑通再说。我见过一个十来人的数据团队,硬要模仿大厂搞数据集市,结果每个集市都半死不活,维护成本高得吓人。
4. 不同规模团队的分层裁剪方案
4.1 小团队(1-3 人):三层足够,别硬凑四层
小团队最大的问题是人力有限,如果硬套四层结构,光是维护 ODS 和 DWD 的同步任务就能把人累垮。我的建议是三层:ODS + DWD + ADS。把 DWS 的聚合逻辑直接放在 DWD 层用视图或者临时表实现,虽然性能差一点,但维护成本低很多。
具体做法:ODS 层保留原始数据,DWD 层做清洗和规范化,ADS 层直接面向报表做聚合。DWS 层的功能通过 DWD 层的宽表 + ADS 层的聚合来分担。等团队扩充到 5 人以上,再把 DWS 层独立出来。
4.2 中型团队(5-15 人):四层标准结构,重点抓规范
这个规模是大多数公司的常态。四层结构(ODS-DWD-DWS-ADS)能跑起来,但关键是建立规范并严格执行。我建议至少定下这几条规矩:
- 每一层的表必须有明确的命名前缀,比如
ods_、dwd_、dws_、ads_。 - 跨层引用必须逐层向上,禁止 DWD 直接查 ODS 以外的层,禁止 ADS 直接查 DWD 以外的层。
- 每一层的数据必须有质量监控,比如空值率、主键唯一性、数据量波动。
这些规矩听起来简单,但执行起来需要工具支撑。我们当时用的是一个自研的元数据管理系统,每次建表都要填表所属层级、责任人、更新频率,不填就不让上线。一开始大家嫌麻烦,后来发现查问题时能快速定位到责任人,效率反而高了。
4.3 大型团队(15 人以上):分层 + 主题域 + 数据治理
大团队的分层会更细,可能会在 DWD 和 DWS 之间再加一层 DWM(Data Warehouse Middle,中间层),用来做轻度汇总。同时会按主题域划分团队,比如交易域、用户域、流量域各自负责自己的 DWD 到 DWS 链路。
这个阶段最重要的是数据治理。分层结构本身不复杂,复杂的是如何保证几十上百人按照同一套规范协作。我们当时的做法是:每个主题域有一个"数据管家",负责审核本域的建表申请和口径定义;跨域的指标由数据平台团队统一管理。这样既保证了灵活性,又避免了口径打架。
5. 落地时最容易踩的五个坑
5.1 坑一:ODS 层做业务清洗,导致无法回溯
前面提过,但值得再强调一次。ODS 层的唯一使命是"忠实记录",任何带业务判断的操作都不应该在这里做。我见过最离谱的案例是:有人在 ODS 层就把"测试用户"过滤掉了,结果后来做用户增长分析时,发现新用户数对不上,查了半天才发现是测试用户里混着真实用户。
5.2 坑二:DWD 层拉链表没处理好,历史状态错乱
拉链表最常见的错误是end_date更新不及时。比如一个客户今天从 A 等级降到 B 等级,拉链表应该把旧记录的end_date改成今天,然后插入一条新记录。但如果更新逻辑写错了,旧记录的end_date还是 9999-12-31,那查询时就会同时命中两条记录,数据直接翻倍。
排查这种问题的技巧:定期跑一致性校验,比如select count(*) from dim_customer where end_date = '9999-12-31'的结果应该等于当前有效客户数。如果对不上,说明拉链断了。
5.3 坑三:DWS 层聚合粒度选错,下游用不了
有个项目我们按"天 + 省份"聚合了交易数据,结果业务方要"按城市看",我们只能重新跑一遍明细。后来学乖了,DWS 层的聚合粒度至少要比最细的分析需求再细一级。比如业务最细要看城市,那 DWS 就按城市聚合;如果业务可能要看门店,那就按门店聚合。
5.4 坑四:ADS 层表太多,维护不过来
ADS 层最容易失控,因为每个报表都想要一张自己的表。我们的对策是:ADS 层的表必须有明确的"退役机制"。每张表上线时登记预期使用周期,到期前一周自动提醒责任人确认是否续用。不续用的表自动下线,释放存储和计算资源。
5.5 坑五:分层规范有了,但没人执行
这是最根本的坑。规范写在文档里,但开发图省事直接跨层查,时间一长分层就名存实亡。解决办法只有一个:把规范嵌入工具链。比如在 SQL 审核环节加规则,发现跨层引用直接打回;在调度系统里配置依赖,DWD 的任务只能依赖 ODS,不能依赖 DWS。让违规操作"做不了",比让人"自觉不做"靠谱得多。
6. 一套可直接抄作业的建表规范模板
6.1 命名规范
| 层级 | 前缀 | 示例 | 说明 |
|---|---|---|---|
| ODS | ods_ | ods_order_info | 源系统表名直接拼接 |
| DWD | dwd_ | dwd_trade_order_detail | 主题_业务过程_明细 |
| DWS | dws_ | dws_trade_user_1d | 主题_粒度_周期 |
| ADS | ads_ | ads_report_region_sales | 应用_报表名 |
6.2 字段规范
- 所有表必须包含
dt分区字段,格式yyyy-MM-dd。 - 金额字段统一用
decimal(18,2),单位统一为元。 - 时间字段统一用
timestamp或string格式yyyy-MM-dd HH:mm:ss。 - 主键字段统一以
_id结尾,比如order_id、user_id。 - 枚举字段必须附带注释,说明每个值的含义。
6.3 更新策略
- ODS:每日增量抽取,保留最近 7 天可重跑。
- DWD:每日全量覆盖或增量合并,拉链表按需更新。
- DWS:每日全量覆盖,按分区写入。
- ADS:按报表需求,可全量可增量。
注意:全量覆盖虽然简单,但数据量大时性能很差。建议超过 1000 万行的表都用增量合并,配合分区裁剪来提升查询效率。
7. 分层之后,怎么验证它真的在起作用
建完分层不是终点,你得有办法验证这套结构是否真的在提升效率。我一般看三个指标:
第一,跨层查询的比例。如果 DWD 的任务大量直接查 ODS,说明 DWD 的覆盖度不够,大家不信任它。理想情况下,跨层查询应该趋近于零。
第二,重复计算的比例。同样的聚合逻辑如果在多个任务里重复出现,说明 DWS 层没建好。我们当时用 SQL 解析工具扫描所有任务,找出重复的子查询模式,然后针对性地下沉到 DWS 层。
第三,需求响应时间。分层建得好,新需求应该能快速找到对应的数据层,而不是每次都要从原始数据开始加工。我们统计过,分层规范执行半年后,简单报表的开发时间从平均 3 天降到了 1 天以内。
最后分享一个我个人坚持的习惯:每季度做一次分层健康度巡检。把所有的表按层级列出来,看看有没有"孤儿表"(没人用也没人维护)、有没有"越权表"(层级和实际用途不符)、有没有"僵尸任务"(调度还在跑但下游已经没人依赖)。这件事花不了多少时间,但能避免数仓随着时间推移变得越来越臃肿。分层结构不是一劳永逸的,它需要持续维护,就像家里的收纳系统,隔段时间不整理就会乱。