4、数据分层设计:ODS、DWD、DWS、ADS 逐层拆解

数据分层这件事,说白了就是给混乱的数据找个家。我刚开始做数仓时,接过一个项目,所有表都堆在一个 schema 里,业务方要个报表得翻半天。后来我痛下决心,按标准分层重构,效果立竿见影。

今天咱们就把 ODS、DWD、DWS、ADS 这四层掰开揉碎讲清楚。每一层该做什么、不该做什么,我踩过的坑也一并告诉你。

核心原则:每一层只解决一个核心问题。ODS 负责「存进来」,DWD 负责「洗干净」,DWS 负责「算好」,ADS 负责「给出去」。别让一层干两层的事。

数仓四层架构图 数仓分层核心架构 数据源:业务库 / 日志 / 埋点 / 外部接口 ODS 层:源数据镜像,不做任何业务清洗 DWD 层:明细清洗,维度退化,一致性处理 DWS 层:轻度汇总,公共指标,宽表预计算 ADS 层:应用集市,报表专用,个性化输出 数据流向:源 → ODS → DWD → DWS → ADS,逐层收敛,逐层抽象 每一层只依赖上一层,禁止跨层引用

4.1 ODS 层设计原则:先存进来再说

ODS 层,全称 Operational Data Store。我习惯叫它「数据码头」——所有数据先靠岸,别管干不干净。

核心原则就一条:保持源数据原样,不做任何业务逻辑清洗。

为什么?因为一旦在 ODS 层做了清洗,你就失去了回溯原始数据的能力。我曾经见过一个项目,ODS 层直接做了字段截断和类型转换,结果后来发现源数据有 bug,想查原始值已经找不回来了。

我的经验:ODS 层只做三件事——字段映射、类型兼容、分区落地。连空值都不要替换,保留原样。源库是 NULL 就存 NULL,源库是空字符串就存空字符串。

ODS 表的设计要点:

  • 全量/增量策略:小表(百万级以下)每天全量拉取;大表用增量 + 分区合并。
  • 分区键选择:按日期分区,dt 字段统一为 yyyyMMdd 格式。
  • 字段命名:保持源字段名,加前缀 src_ 区分。比如 src_order_id、src_user_name。
  • 数据质量标记:加一个 is_valid 字段,标记解析失败或格式异常的行。
-- ODS 层建表示例(Hive)
CREATE TABLE ods.ods_order (
    src_order_id      STRING,
    src_user_id       STRING,
    src_product_id    STRING,
    src_amount        DECIMAL(18,2),
    src_order_time    STRING,
    src_status        STRING,
    etl_load_time     TIMESTAMP,
    is_valid          INT COMMENT '1=有效 0=解析异常'
)
PARTITIONED BY (dt STRING)
STORED AS ORC;

注意:ODS 层不要建索引,不要做聚合,不要做关联。它的唯一使命就是「存」。查询性能问题交给下游层解决。

4.2 DWD 层设计:把数据洗干净

DWD 层,明细数据层。这是整个数仓最核心的一层,也是工作量最大的一层。

我个人的习惯是,DWD 层要完成三件事:清洗、维度退化、一致性处理。

清洗——把 ODS 层那些乱七八糟的数据规整好。比如时间字段统一成 TIMESTAMP 类型,金额字段去掉货币符号,状态码映射成中文描述。

维度退化——把常用的维度属性直接拉到事实表里。比如订单表里直接带上用户等级、商品分类、店铺名称,省得每次查询都要 join 维度表。

一致性处理——统一度量单位、统一编码规则、统一空值策略。比如所有金额字段统一为 DECIMAL(18,2),所有状态字段统一用 int 码表。

处理类型 ODS 原始值 DWD 处理后 说明
时间格式化 2024-01-15 14:30:22 2024-01-15 14:30:22.000 统一为 TIMESTAMP
金额清洗 ¥1,234.56 1234.56 去掉货币符号和千分位
状态映射 1 / 2 / 3 已支付 / 已发货 / 已完成 关联码表转义
空值处理 NULL / '' / 'N/A' NULL(统一) 只保留一种空值语义
-- DWD 层建表示例
CREATE TABLE dwd.dwd_order_detail (
    order_id          STRING,
    user_id           STRING,
    user_level        STRING COMMENT '维度退化:用户等级',
    product_id        STRING,
    product_category  STRING COMMENT '维度退化:商品分类',
    shop_name         STRING COMMENT '维度退化:店铺名称',
    amount            DECIMAL(18,2),
    order_time        TIMESTAMP,
    order_status      STRING,
    dt                STRING
)
PARTITIONED BY (dt STRING)
STORED AS ORC;

避坑指南:我曾经在 DWD 层做过一次大范围的维度退化,把 20 多个维度属性全拉到事实表里,结果表宽超过 200 列,查询效率反而下降了。后来我总结:维度退化只保留查询频率最高的 5-8 个属性,其余的保持星型模型 join 即可。

4.3 DWS 层设计:公共指标预计算

DWS 层,汇总数据层。说白了就是「算好等查」。

为什么需要这一层?你想想看,如果每个报表都从 DWD 层几亿条明细里做聚合,那得跑多久?DWS 层就是把这些公共的、高频的聚合结果提前算好,存成宽表。

DWS 层的设计要点:

  • 粒度选择:通常按天 + 维度组合。比如「每天每个用户的订单汇总」「每天每个商品的销售汇总」。
  • 指标定义:所有指标必须有明确的业务口径和计算公式,不能有歧义。
  • 公共性:只放被多个下游场景复用的指标。如果某个指标只有一个报表用,放到 ADS 层去算。
-- DWS 层建表示例:用户日汇总宽表
CREATE TABLE dws.dws_user_daily (
    user_id           STRING,
    dt                STRING,
    order_count       BIGINT COMMENT '下单次数',
    order_amount      DECIMAL(18,2) COMMENT '下单金额',
    pay_count         BIGINT COMMENT '支付次数',
    pay_amount        DECIMAL(18,2) COMMENT '支付金额',
    refund_count      BIGINT COMMENT '退款次数',
    refund_amount     DECIMAL(18,2) COMMENT '退款金额',
    login_count       BIGINT COMMENT '登录次数',
    browse_count      BIGINT COMMENT '浏览商品次数',
    etl_update_time   TIMESTAMP
)
PARTITIONED BY (dt STRING)
STORED AS ORC;

我的习惯:DWS 层表名统一加前缀 dws_,字段名用下划线分隔,指标字段后面加注释说明口径。这样新同事接手时不用猜「这个 amount 到底是下单金额还是支付金额」。

4.4 ADS 层设计:按需定制,灵活输出

ADS 层,应用数据层。这是离业务最近的一层,也是变化最频繁的一层。

ADS 层没有固定的设计模式,完全按业务需求来。可能是宽表、可能是多维分析表、也可能是接口专用的 KV 结构。

设计原则就两条:

  1. 按需构建:业务方要什么,你就给什么。不要提前造一些「可能有用」的表。
  2. 轻量化:ADS 层表尽量窄,字段控制在 20 个以内。太宽的表说明你还没想清楚业务到底要什么。
-- ADS 层示例:运营日报宽表
CREATE TABLE ads.ads_operation_daily (
    report_date       STRING,
    new_user_count    BIGINT,
    active_user_count BIGINT,
    order_user_count  BIGINT,
    total_order_count BIGINT,
    total_order_amt   DECIMAL(18,2),
    avg_order_amt     DECIMAL(18,2),
    refund_rate       DECIMAL(5,4),
    etl_update_time   TIMESTAMP
)
PARTITIONED BY (dt STRING)
STORED AS ORC;

注意:ADS 层不要做复杂的 ETL 逻辑。它应该只做简单的字段筛选和格式转换。复杂的业务逻辑放到 DWD 和 DWS 层去处理,ADS 层只负责「取数」。

4.5 各层对比与协作关系

最后,我用一张表总结四层的核心差异:

分层 核心职责 数据粒度 存储周期 使用方
ODS 源数据镜像 原始粒度 7-30 天 DWD 层、数据回溯
DWD 清洗 + 维度退化 业务明细 30-90 天 DWS 层、临时查询
DWS 公共指标汇总 轻度聚合 90-365 天 ADS 层、BI 工具
ADS 应用定制输出 按需聚合 按业务需求 报表、接口、大屏

嗯,四层设计讲完了。记住一句话:ODS 存、DWD 洗、DWS 算、ADS 给。各层职责清晰,协作才能高效。下一层只依赖上一层,别搞出循环依赖,那是灾难。