
1. 从一次数据口径吵架说起数仓分层的本质是什么刚入行那会儿我参与过一个零售项目的数仓建设。上线第二周运营和财务就吵起来了——同一笔订单金额报表A显示退款后净额报表B显示原始交易额两个数字差了十几万。排查了一整天最后发现是两份报表各自从原始日志表里用不同的过滤条件算出来的。这件事让我第一次真正理解了数仓分层存在的意义它不是为了画架构图好看而是为了让同一个指标只有一种算法这件事在工程上可落地。数仓分层说白了就是把数据从原始状态到可消费状态的加工过程切成几个职责明确的阶段。每一层只干自己该干的事上层不越级去碰下层的原始数据下层也不关心上层怎么展示。这跟做菜很像采购回来的食材ODS先做清洗切配CDM再按菜谱炒成成品ADS而不是每个厨师都直接从菜市场拎菜下锅。关键词里提到的ODS、CDM、ADS就是这套分层体系里最核心的三层骨架。ODS 负责原封不动地接进来CDM 负责洗干净、理清楚、按主题归好类ADS 负责按业务需求组装成能直接用的结果。中间还可能细分出 DWD明细层和 DWS汇总层但本质上都属于 CDM 的范畴。这篇文章适合三类人看刚接触数仓、被分层概念绕晕的初学者正在设计或重构数仓分层、拿不准边界怎么划的工程师以及需要跟数据团队对接、想搞明白为什么取个数要走这么多层的业务方。我会从每层的职责边界、建模方法、实操踩坑三个角度展开尽量把那些文档里不会写的经验讲透。提示分层不是目的可维护性和口径一致性才是。如果一个分层方案让开发效率变低了、口径反而更乱了那这个分层就是失败的不管它看起来多标准。2. ODS 层接数据这件事比你想的讲究得多2.1 ODS 的定位只做搬运不做加工ODSOperational Data Store层的核心原则只有一条尽可能保留源系统的原始面貌不做业务逻辑加工。源系统是什么字段、什么类型、什么值ODS 就存什么。哪怕源系统里有个字段叫flag值是0/1/2/9这种看不懂的编码ODS 也原样保留解码的事留给下游。为什么这么设计因为一旦你在 ODS 层做了清洗或转换后面发现转换逻辑有问题你就没有原始真相可以回溯了。我见过一个团队在 ODS 层就把时间字段统一转成了东八区结果后来接入了海外业务数据时区信息全丢了只能重新从源系统抽一遍历史数据代价极大。ODS 层通常采用贴源表结构也就是和源系统表结构基本一一对应。表命名上一般加前缀标识来源比如ods_mysql_order_info、ods_log_user_behavior让人一眼能看出数据从哪来、什么类型。2.2 抽取策略全量和增量怎么选接数据的方式直接决定了 ODS 层的质量和成本。常见的抽取策略有三种各有适用场景策略适用场景优点风险点全量抽取小表、维表、数据量稳定的表实现简单不怕漏数据大表全量抽会拖垮源库增量抽取时间戳有更新时间字段的业务表对源库压力小源系统时间戳不准会漏数增量抽取binlog高实时要求、无法加时间戳的表实时性好不侵入源库运维复杂度高我个人的经验是维表和小表走全量事实表走增量核心交易表尽量上 binlog。时间戳增量看起来简单但坑特别多——有些源系统的update_time只在业务操作时更新数据库层面的批量修数据不会触发它结果就是数据悄悄漏了你还不知道。还有一个容易被忽略的点ODS 层一定要加分区。按天分区是最常见的做法分区字段用数据抽取日期etl_date而不是业务日期。这两者的区别在于如果今天抽的是昨天产生的数据etl_date是今天业务日期是昨天。用etl_date分区的好处是重跑某一天的数据时直接覆盖对应分区就行不会影响其他天的数据。2.3 ODS 层的常见坑与应对坑一源表结构变更没有感知。源系统加了个字段、改了个类型ODS 抽取任务直接报错或者静默丢字段。应对办法是建立 schema 变更监控每次抽取前对比源表和 ODS 表的结构差异有变化就告警。坑二抽取频率和源库压力打架。业务方要求 5 分钟抽一次DBA 说源库扛不住。这时候可以考虑读从库、错峰抽取、或者对非核心表降低频率。别硬扛硬扛的结果往往是源库出问题大家一起背锅。坑三历史数据初始化。新接一个源系统时历史数据怎么灌全量灌一次通常跑不动建议按时间分批灌比如每次灌一个月控制单次数据量。灌完之后再切到增量模式中间要做好衔接避免重复或遗漏。注意ODS 层不是垃圾堆。虽然不做业务加工但基本的规范性还是要有的——字段类型要合理、分区要规范、表要有注释。我见过 ODS 层表连个注释都没有新人接手完全不知道每个字段什么意思这种原始是懒惰不是规范。3. CDM 层数仓真正的加工车间3.1 CDM 的拆分逻辑DWD 和 DWS 各管什么CDMCommon Data Model是数仓分层的核心它通常进一步拆成两层DWDData Warehouse Detail明细层和DWSData Warehouse Summary汇总层。这两层的分工决定了整个数仓的复用性和查询性能。DWD 层做的是明细级别的清洗和规范化去重、空值处理、字段解码、单位统一、维度关联。它的产出仍然是明细数据一行对应一条业务事实但已经是干净、可理解的明细了。比如 ODS 里order_status是1/2/3DWD 里就变成待支付/已支付/已取消。DWS 层做的是轻度汇总按主题用户、商品、订单等和维度时间、地区、渠道等做聚合产出宽表。它的价值在于很多报表和分析都基于相同的汇总口径如果每个报表都从 DWD 现算既慢又容易口径不一致。DWS 把这些公共汇总沉淀下来上层直接取用。用一个类比DWD 是把食材洗净切好的净菜DWS 是按常见菜式预先搭配好的半成品套餐ADS 是最后炒出来的成品菜。半成品套餐的好处是今天做宫保鸡丁、明天做辣子鸡都能用上同一份切好的鸡丁。3.2 维度建模星型模型在 DWD 层的落地DWD 层建模最常用的方法是维度建模核心是事实表和维度表。事实表存业务过程的度量值金额、数量、次数维度表存描述性属性时间、地区、用户、商品。星型模型和雪花模型的选择是个老生常谈的问题。我的建议很直接优先星型除非维度表特别大且冗余严重。星型模型维度表不做进一步规范化会有冗余但查询时 join 少、性能好、理解成本低。雪花模型虽然省存储但多层 join 在数仓场景下往往得不偿失尤其是当你的查询引擎不是列存的时候。举个具体例子。订单事实表dwd_order_detail关联的维度包括dim_date日期维度包含年、季、月、周、是否节假日等dim_user用户维度包含注册渠道、会员等级、所在城市等dim_product商品维度包含品类、品牌、价格带等dim_shop店铺维度包含店铺类型、所属区域等每个维度表都有一个代理键surrogate key事实表存代理键而不是自然键。这样做的好处是当源系统的自然键发生变化时只需要更新维度表的映射关系事实表不用动。3.3 缓慢变化维处理会变的维度维度属性会随时间变化比如用户的会员等级从银卡变成金卡商品的价格带从中端变成高端。怎么在数仓里记录这种变化就是缓慢变化维SCD要解决的问题。最常见的三种处理方式SCD Type 1直接覆盖不保留历史直接更新为新值。适合那些不需要追溯历史的属性比如用户的手机号更正。SCD Type 2拉链表保留历史每条变化生成新记录用start_date和end_date标记有效期。适合需要追溯当时是什么状态的属性比如用户等级、商品归属品类。SCD Type 3增加列增加一列存上一个值只保留有限历史。实际用得比较少。拉链表是数仓里最经典的 SCD 实现但也是最容易出问题的。我踩过的坑包括end_date用了9999-12-31导致查询时范围判断写错、同一天多次变更导致记录重复、以及拉链表和事实表关联时忘了加时间范围条件导致数据膨胀。这些坑的共同点是逻辑本身不难难的是每次关联时都要记得带上时间条件。提示拉链表的end_date建议用9999-12-31表示当前有效但查询时一定要写where dt between start_date and end_date别偷懒只写where end_date 9999-12-31否则历史数据就查不到了。3.4 DWS 层汇总宽表不是越宽越好DWS 层的宽表设计很多人容易走极端——恨不得把所有维度都塞进去做成一张万能宽表。结果就是表巨大、更新慢、字段几百个真正用到的没几个。我的经验是按分析主题拆分每个主题一张或几张宽表维度控制在 10-15 个以内。比如交易主题的 DWS 表核心维度就是时间、用户、商品、店铺、渠道这几个其他维度如果偶尔用到可以在 ADS 层再关联。DWS 层的汇总粒度也要想清楚。是按天汇总还是按小时是按用户汇总还是按商品汇总粒度越细灵活性越高但数据量越大粒度越粗查询越快但灵活性越差。通常的做法是按天 按核心维度组合比如dws_trade_user_day用户日汇总、dws_trade_shop_day店铺日汇总。如果业务有小时级分析需求再单独建小时粒度的汇总表。4. ADS 层离业务最近也最容易被做脏4.1 ADS 的职责边界面向场景不做通用ADSApplication Data Store层是直接面向报表、看板、接口的数据层。它的特点是场景化——每个 ADS 表通常对应一个或一组具体的分析需求而不是追求通用性。这跟 DWD/DWS 的设计思路是相反的。DWD 和 DWS 追求的是一次加工多次复用所以要做通用化设计ADS 追求的是快速响应业务需求所以可以为了性能做冗余、为了展示做预计算。比如一个每日销售概览看板需要展示今日 GMV、订单量、客单价、同比环比。ADS 层就可以建一张ads_sales_overview_day表把这些指标都算好看板直接查这一张表就行不用 join 任何其他表。这种宽表化、预计算的做法在 ADS 层是完全合理的。4.2 ADS 层的指标一致性怎么保证ADS 层最大的风险是指标口径不一致。同一个活跃用户数报表 A 算的是登录过的用户报表 B 算的是有下单行为的用户两个数对不上业务方就懵了。解决这个问题的关键是在 DWS 层就把核心指标的口径定义清楚ADS 层只做引用不做重新定义。具体做法包括建立指标字典每个指标明确名称、口径、计算公式、数据来源DWS 层为每个核心指标提供统一的汇总表ADS 层直接取用如果 ADS 层确实需要重新计算必须在指标字典里单独定义不能和已有指标混用我见过一个团队ADS 层有 20 多张表都算了订单量但口径各不相同——有的含取消订单有的不含有的按创建时间有的按支付时间。后来他们花了两个月做指标治理才把这些口径统一起来。这个教训说明ADS 层的灵活是有代价的没有约束的灵活就是混乱。4.3 从 DWS 到 ADS什么时候该新建表不是每个报表需求都要新建 ADS 表。判断标准可以简化为三条这个需求是否已有 ADS 表能覆盖如果能直接复用别新建。这个需求的查询频率高吗如果只是临时看一次直接从 DWS 查就行不用落 ADS 表。这个需求的性能要求高吗如果 DWS 查询能在可接受时间内返回也不用落 ADS。只有当需求高频、性能要求高、且现有 ADS 表无法覆盖时才值得新建 ADS 表。否则 ADS 层会迅速膨胀变成一堆没人维护的僵尸表。5. 分层落地时最容易踩的五个坑5.1 坑一分层太细开发效率反而下降有些团队把分层做到了五六层甚至七八层ODS、DWD、DWM、DWS、DWT、ADS……每层之间还要建一堆中间表。结果是一个简单的指标要从 ODS 一路加工到 ADS中间经过七八个任务任何一个环节出问题整个链路就断了。我的建议是中小团队三层ODS、CDM、ADS就够了大团队最多五层ODS、DWD、DWS、DWT、ADS。层数越多维护成本越高收益递减。分层是为了解决问题不是为了显得专业。5.2 坑二跨层引用把分层做成了摆设分层规范里最重要的一条是不能跨层引用——ADS 不能直接查 ODSDWD 不能直接查源系统。但实际开发中为了赶进度跨层引用太常见了。跨层引用的危害在于它绕过了中间层的清洗和规范化导致口径不一致、数据质量不可控。而且一旦下层结构变化跨层引用的任务就会直接报错排查起来很麻烦。应对办法在调度系统里做依赖校验发现跨层依赖就告警。同时在代码 review 时把跨层引用作为重点检查项。技术上可以用数据血缘工具自动检测但最根本的还是团队要形成共识。5.3 坑三分区设计不合理查询慢还费钱分区是数仓性能的命脉。分区设计不合理轻则查询慢重则全表扫描把资源跑满。常见的分区问题包括分区字段选错用业务日期分区但查询按抽取日期过滤导致分区裁剪失效分区粒度过细按小时分区但数据量很小产生大量小文件分区粒度过粗按月分区但查询按天过滤每次都要扫整月数据我的经验是事实表按天分区维表按天或按全量快照分区日志类数据按天 小时分区。分区字段要和常用查询条件对齐别为了看起来规范选一个没人用的字段做分区。5.4 坑四元数据管理缺失表多了就失控数仓表数量一旦超过几百张没有元数据管理就是灾难。新人不知道表在哪、字段什么意思、数据从哪来、更新频率如何只能靠问老人老人一走就抓瞎。元数据管理至少要做到每张表有清晰的表注释和字段注释记录表的负责人、更新频率、数据量级维护数据血缘能追溯上下游建立表生命周期管理定期清理无用表这些事看起来琐碎但长期收益巨大。我见过一个团队因为元数据缺失重复建了十几张功能相同的表浪费了大量存储和计算资源。5.5 坑五只建不管数据质量没人负责分层建好了任务跑起来了但数据质量没人管。今天这个字段空了明天那个指标异常了业务方发现了才来问数据团队才知道出问题了。数据质量监控应该覆盖几个关键维度监控维度检查内容告警方式完整性主键是否为空、关键字段空值率邮件 即时消息唯一性主键是否重复邮件 即时消息及时性任务是否按时完成即时消息准确性指标波动是否超阈值邮件 即时消息一致性跨表同一指标是否一致邮件监控规则不用一开始就追求大而全先把核心表的完整性、及时性、准确性监控起来再逐步扩展。关键是有人看告警、有人处理告警否则监控就是摆设。6. 一套可落地的分层规范长什么样6.1 命名规范让表名自己说话表命名规范是分层落地的第一步。一个好的命名应该能让人一眼看出这表属于哪层、什么主题、什么粒度、更新频率如何。推荐的分层前缀ODS 层ods_ 源系统标识 表名如ods_mysql_order_infoDWD 层dwd_ 主题 表名如dwd_trade_order_detailDWS 层dws_ 主题 粒度 周期如dws_trade_user_dayADS 层ads_ 场景 周期如ads_sales_overview_day字段命名也要统一日期用_date或_dt后缀金额用_amt后缀数量用_cnt后缀标识用_id后缀。别今天用order_amount明天用order_amt后天用order_money维护起来会疯。6.2 任务调度依赖关系要清晰分层之后任务之间的依赖关系会变得复杂。ODS 任务完成后才能跑 DWDDWD 完成后才能跑 DWSDWS 完成后才能跑 ADS。这个依赖链必须由调度系统自动管理不能靠人工触发。调度设计上要注意几点任务优先级核心链路的任务优先级要高确保按时产出失败重试网络抖动等临时故障要自动重试重试次数和间隔要合理依赖检查上游任务没完成下游任务不能启动超时告警任务运行超过预期时间要告警别等到业务方来催6.3 数据质量从事后救火到事前预防数据质量管理的最高境界是事前预防而不是事后救火。具体做法包括源头校验ODS 抽取时校验数据量、主键唯一性异常就阻断过程校验DWD/DWS 加工时校验关键字段空值率、指标波动范围结果校验ADS 产出后校验核心指标是否在合理区间对账机制核心指标和源系统或业务方对账确保一致对账是最有效但也最费力的手段。建议只对核心指标做对账比如 GMV、订单量、用户数不用所有指标都对。对账频率可以是每天一次发现问题及时排查。7. 分层之后怎么验证它真的有用分层建完了怎么判断这套分层是不是成功我的判断标准有三条第一新人能不能在一天内看懂数据流向。如果新人拿到数仓文档能顺着 ODS → DWD → DWS → ADS 的链路搞清楚一个指标是怎么算出来的说明分层是清晰的。如果新人看了一天还是一头雾水那分层大概率有问题。第二同一个指标在不同报表里是不是同一个数。这是分层最核心的价值。如果分层之后不同报表的同一指标还是对不上那分层就没起到作用需要回头检查口径定义和跨层引用问题。第三加一个新指标需要改几张表。如果加一个指标只需要在 ADS 层加一张表或几个字段说明分层复用性好。如果加一个指标要从 ODS 一路改到 ADS改七八张表说明分层设计有问题公共层没有沉淀好。这三条标准看起来简单但真正能做到的团队不多。我见过太多数仓分层图画得很漂亮实际用起来还是各查各的分层成了面子工程。分层不是画出来的是建出来的更是用出来的。最后分享一个我在实际项目中的小技巧在 DWS 层为核心指标建一张指标快照表每天把关键指标的值存一份包括指标名、日期、数值、口径说明。这样当业务方质疑为什么这个数和昨天不一样时可以直接查快照表对比快速定位是数据问题还是口径问题。这张表不大但省下的沟通成本非常可观。