1. 数据分层不是概念游戏是数据仓库的骨架刚入行做数据那会儿我最烦听到的就是“分层”两个字。总觉得这是架构师们为了画架构图好看硬生生造出来的一堆缩写——ODS、DW、DM听着就头大。直到有一次我接手了一个“数据中台”项目上游业务库有几十张表下游报表需求上百个每天跑批时间窗口只有四个小时。那时候我才真正理解分层不是画着玩的它是数据仓库的骨架是让整个数据流从一团乱麻变成有序管道的唯一办法。数据分层说白了就是给数据从“生”到“用”的过程划分出不同的加工车间。原始数据从业务系统抽过来先放进一个“原料仓库”这就是ODSOperational Data Store操作数据存储。原料仓库里的东西基本不动保持和业务系统一样的模样方便追溯和核对。然后这些原料被送到“加工车间”进行清洗、整合、关联变成干净、规范、有主题的中间层这就是DWData Warehouse数据仓库。最后加工好的半成品或成品被送到“成品展厅”按照不同的业务需求组装成报表、标签、指标直接给业务方看这就是DMData Mart数据集市。这套逻辑听起来简单但真正落地的时候坑一个接一个。比如ODS 层到底要不要做清洗DW 层怎么划分主题域DM 层是每个业务线一个还是统一一个这些问题没有标准答案只有适合当前团队和业务阶段的答案。我见过太多团队要么分层太细维护成本高到离谱要么分层太粗ODS 直接对接报表改一个字段牵动全身。所以这篇文章我想从实际操盘的角度把 ODS、DW、DM 这三层到底怎么分、怎么建、怎么避坑掰开揉碎了讲清楚。不管你是刚接触数据仓库的新人还是正在重构数据链路的老人应该都能找到一些能直接抄作业的东西。2. ODS 层别急着洗干净先原汁原味存下来2.1 ODS 的核心定位是“历史快照数据溯源”很多人一上来就想在 ODS 层做数据清洗把脏数据、空值、格式问题全处理掉。我的建议是千万别。ODS 层的核心价值在于“还原”它要能回答“昨天下午三点这笔订单在业务系统里长什么样”这种问题。如果你在 ODS 层就把数据改了一旦下游报表对不上你连排查的原始依据都没有。我一般会把 ODS 层设计成“贴源”结构也就是和业务库表结构基本保持一致只做极少的标准化处理比如统一字段命名风格、加上etl_time这样的抽取时间戳。这样做的好处是任何数据问题都能回溯到 ODS 层和业务方对账的时候直接拿 ODS 的数据和业务系统比一目了然。注意ODS 层的数据保留周期要根据业务合规要求和存储成本来定。一般建议至少保留 3 到 6 个月金融、医疗等强监管行业可能需要保留更久。2.2 抽取策略全量还是增量这是个问题ODS 层的数据怎么从业务库抽过来是第一个要做的技术决策。全量抽取简单粗暴每天把整张表拉一遍但数据量大了之后对业务库和网络的压力都很大。增量抽取效率高但需要业务表有可靠的时间戳或自增 ID而且处理更新和删除操作时容易出问题。我自己的经验是小表全量大表增量核心表做拉链。比如配置表、维度表这种几千几万行的直接全量覆盖省事又不容易错。订单、流水这种每天几百万上千万的必须走增量一般用update_time作为水位线。但这里有个坑业务系统里的update_time不一定可靠有些批量操作不会更新这个字段导致增量抽取漏数据。所以我通常会配合源库的 binlog 或者 CDC 工具来做兜底确保数据不丢。-- 一个典型的增量抽取 SQL 示例 SELECT * FROM source_order WHERE update_time ${last_extract_time} AND update_time ${current_extract_time};这个 SQL 看起来简单但${last_extract_time}的维护很关键。我一般会在 ODS 层建一张etl_watermark表记录每张源表上次抽取的最大时间戳每次跑批先读水位线抽完再更新。这样即使任务失败重跑也不会重复抽数据。2.3 ODS 层的分区与存储优化ODS 层的数据量往往是整个仓库里最大的所以分区设计直接影响到查询性能和存储成本。我通常按日期做一级分区因为绝大多数抽取任务都是按天调度的。如果单日数据量特别大比如超过 1 亿行可以考虑再按业务线或地区做二级分区。存储格式上如果用的是 Hive 或 Spark 生态我强烈建议用ORC 或 Parquet这种列式存储压缩比高查询时只读需要的列速度比 TextFile 快好几倍。如果用的是关系型数据库做 ODS那就要注意索引的设计但索引太多又会影响写入性能需要根据查询模式权衡。存储格式压缩比查询速度写入速度适用场景TextFile低慢快临时数据、调试ORC高快中大规模数仓、分析场景Parquet高快中嵌套数据结构、Spark 生态Avro中中快行级写入、流式场景3. DW 层从“原料”到“半成品”的加工车间3.1 主题域划分别按部门分按业务过程分DW 层是整个数据仓库的核心也是最容易做歪的地方。我见过不少团队直接按业务部门来划分主题域比如“销售域”、“财务域”、“运营域”。这种分法看起来清晰但实际上后患无穷。因为业务部门是会变的组织架构一调整主题域就得跟着改下游所有任务都要动。更合理的做法是按业务过程来划分主题域。比如电商场景核心业务过程就是“下单”、“支付”、“发货”、“退款”、“评价”。每个业务过程对应一个事实表围绕这个事实表再去关联相关的维度比如用户、商品、门店、时间。这样划分出来的主题域是稳定的不会因为组织架构调整而改变。我一般会把 DW 层再细分为DWD数据仓库明细层和DWS数据仓库汇总层。DWD 层保持和 ODS 层一样的粒度但做了清洗、规范化、维度退化等处理相当于“精加工后的明细”。DWS 层则按主题进行轻度汇总比如“用户日粒度消费汇总”、“商品日粒度销售汇总”为上层 DM 提供更高效的查询基础。3.2 维度建模星型模型还是雪花模型维度建模是 DW 层的核心方法论最常用的就是星型模型和雪花模型。星型模型以一个事实表为中心周围直接关联多个维度表维度表不再规范化结构简单查询性能好。雪花模型则是对维度表进一步规范化拆成多个层级节省存储但查询时 Join 更多性能会下降。我的建议是优先用星型模型。现在存储成本已经很低了没必要为了省那点空间去牺牲查询性能。而且星型模型对业务人员更友好他们看表结构就能理解数据关系。只有在维度特别大、冗余特别严重的情况下才考虑雪花模型。提示维度表的主键设计很关键。我一般会用代理键surrogate key而不是业务主键。代理键用自增整数或哈希值避免业务主键变更导致的历史数据关联断裂。3.3 缓慢变化维Type 1 还是 Type 2缓慢变化维SCD是维度建模里绕不开的话题。最简单的 Type 1 是直接覆盖不保留历史。Type 2 是拉链式每次变化新增一条记录用start_date和end_date标记有效期。还有 Type 3 是加一列“前值”但用得比较少。我的经验是核心维度用 Type 2普通维度用 Type 1。比如用户维度用户的等级、城市、手机号可能会变如果业务方需要分析“用户在下单时的等级”那就必须用 Type 2保留历史快照。而像商品分类这种如果业务方不关心历史分类直接用 Type 1 覆盖就行维护成本低很多。-- Type 2 缓慢变化维的更新逻辑示例 -- 当检测到维度属性变化时 UPDATE dim_user SET end_date ${current_date}, is_current 0 WHERE user_id ${user_id} AND is_current 1; INSERT INTO dim_user (user_id, user_level, city, start_date, end_date, is_current) VALUES (${user_id}, ${new_level}, ${new_city}, ${current_date}, 9999-12-31, 1);这个逻辑看起来简单但在实际跑批时要注意并发更新的问题。如果多个任务同时更新同一个维度可能会产生重复的当前记录。我一般会给维度表加一个唯一约束或者用事务保证原子性。4. DM 层让业务方看得懂、查得快4.1 DM 层的本质是“面向场景的预计算”DM 层是直接面向业务应用的所以它的设计原则只有一个怎么让业务方用得爽。业务方不关心你的数据怎么分层、怎么建模他们只关心“我要的指标能不能快速查出来”、“报表能不能按时出”。所以DM 层的核心工作就是预计算。把业务方常用的指标、维度组合提前在 DW 层的基础上算好存成宽表或者聚合表。比如“每日各品类销售额”、“每月各渠道新增用户数”这些指标如果每次都从 DWD 层现算跑批时间会很长。提前算好放在 DM 层报表查询就是简单的SELECT秒级返回。我一般会把 DM 层分成两类指标型 DM和标签型 DM。指标型 DM 主要存聚合指标比如销售额、订单量、转化率按不同维度组合预计算。标签型 DM 主要存用户标签、商品标签比如“高价值用户”、“潜在流失用户”供推荐系统或营销系统调用。4.2 宽表还是聚合表根据查询模式来定DM 层到底是做成宽表还是聚合表取决于业务方的查询模式。如果业务方经常查明细比如“某个订单的详情”那就需要宽表把订单相关的用户、商品、支付信息都打平到一张表里。如果业务方主要看汇总数据比如“本月销售额”那就用聚合表按维度组合预计算。我自己的做法是两者都做但控制数量。宽表不要超过 20 张聚合表不要超过 50 张否则维护成本会失控。每张表都要有明确的业务方和查询场景没有明确使用者的表坚决不建。DM 表类型粒度查询模式典型场景明细宽表订单/用户/商品点查、详情订单详情、用户画像聚合表日/月/品类/渠道汇总、趋势日报、月报、看板标签表用户/商品圈选、推送精准营销、推荐4.3 DM 层的刷新策略全量还是增量DM 层的刷新策略也很讲究。全量刷新简单每天把整张表重算一遍但数据量大时跑批时间会很长。增量刷新只算变化的部分效率高但逻辑复杂容易出错。我的经验是小表全量大表增量指标表按分区覆盖。比如标签表用户量几百万全量刷新也就几分钟那就全量。订单聚合表每天几千万行那就按日期分区每天只覆盖当天的分区。这样即使某天任务失败重跑也只影响当天不会把历史数据搞乱。注意增量刷新一定要处理好迟到数据。比如订单支付后可能隔几天才退款如果 DM 层只算当天的数据退款金额就统计不到。我一般会在 DM 层保留最近 7 到 30 天的滚动窗口每天重算这个窗口内的数据确保迟到数据能被修正。5. 分层落地时最容易踩的五个坑5.1 坑一ODS 层直接对接报表这是最常见的错误。有些团队为了省事报表直接查 ODS 层的数据。短期看没问题但时间一长业务方要加个指标、改个口径你就得去改 ODS 的抽取逻辑而 ODS 是贴源的改了就破坏了“还原”的原则。更严重的是ODS 层没有清洗和整合不同业务系统的同名字段含义可能完全不同报表数据很容易出错。正确的做法是任何报表都必须经过 DW 或 DM 层。ODS 只负责存储原始数据不承担任何分析职责。这样即使业务系统升级、表结构变更也只需要调整 ODS 到 DW 的加工逻辑不会影响下游报表。5.2 坑二DW 层主题域划分过细有些团队把主题域划分得特别细比如“销售域”下面再分“线上销售”、“线下销售”、“海外销售”每个子域一套 DWD 和 DWS 表。结果就是表数量爆炸维护成本极高而且很多表的数据量很小跑批效率反而下降。我的建议是主题域划分要适度。一般中小型数仓5 到 8 个主题域就够了。每个主题域下面DWD 层按业务过程建表DWS 层按分析维度建表。不要为了“看起来清晰”而过度拆分。5.3 坑三DM 层表没有明确的生命周期DM 层的表如果只增不减几年下来就是几百上千张表没人知道哪些还在用、哪些可以删。我见过一个团队DM 层有 800 多张表实际每天被查询的不到 100 张剩下的全是“僵尸表”占着存储和计算资源。我的做法是每张 DM 表都要有负责人和过期时间。建表的时候就要登记谁用、用来做什么、预计使用多久。每季度做一次盘点超过 3 个月没有查询记录的表直接下线。这样能保持 DM 层的精简和高效。5.4 坑四忽略数据质量监控分层架构建好了但如果数据质量没有监控那就是“金玉其外败絮其中”。我经历过一次事故ODS 层抽取任务失败但下游 DW 和 DM 层没有感知继续用旧数据跑结果报表出了三天的错误数据业务方拿着报表去开会闹了大笑话。从那以后我在每一层都加了数据质量监控。ODS 层监控抽取行数、主键唯一性DW 层监控关联率、空值率DM 层监控指标波动。一旦发现异常立即告警并阻断下游任务。这套机制虽然增加了开发量但避免了无数次数据事故。5.5 坑五元数据管理缺失分层架构里表与表之间的依赖关系非常复杂。如果没有元数据管理你根本不知道改一张 ODS 表会影响哪些 DW 和 DM 表。我见过一个团队因为改了一个 ODS 字段的类型导致下游几十张表跑批失败排查了一整天才找到原因。所以元数据管理必须从第一天就做。至少要有表级和字段级的血缘关系能回答“这张表的数据从哪来”、“这张表被哪些表用了”。现在有很多开源工具可以做这件事比如 Atlas、DataHub选一个适合自己技术栈的尽早接入。6. 一套可复用的分层规范模板6.1 命名规范让表名自己说话命名规范是分层落地的第一步。我一般会用这样的格式{层级}_{主题域}_{业务过程}_{刷新频率}。比如dwd_trade_order_di表示“数据仓库明细层_交易域_订单_日增量”。这样一看表名就知道它在哪一层、属于哪个主题、什么粒度、怎么刷新。层级前缀示例说明ODSods_ods_order贴源保持原样DWDdwd_dwd_trade_order_di明细日增量DWSdws_dws_trade_user_1d汇总日粒度DMdm_dm_sales_category_1d集市日粒度6.2 任务调度依赖关系要清晰分层架构的任务调度核心是依赖关系。ODS 任务完成后才能跑 DW 任务DW 任务完成后才能跑 DM 任务。我一般会用调度工具比如 Airflow、DolphinScheduler来管理这些依赖每个任务配置上游依赖上游失败则下游不跑。这里有个小技巧给关键任务加 SLA。比如 ODS 抽取必须在凌晨 2 点前完成DW 加工必须在 4 点前完成DM 刷新必须在 6 点前完成。一旦超时立即告警留出时间人工干预。6.3 数据权限分层管控数据权限也要按层管控。ODS 层的数据最敏感只对数据开发人员开放DW 层对数据分析师开放DM 层对业务方开放。这样既能保证数据安全又能让不同角色的人各取所需。我一般会用视图或行级权限来实现。比如业务方只能看 DM 层的表而且只能看自己部门的数据。数据分析师可以看 DW 和 DM 层但不能看 ODS 层。数据开发人员有全部权限但操作要审计。7. 分层之后怎么衡量做得好不好分层架构建完了怎么判断它是不是成功我一般会看三个指标跑批时长、查询性能、维护成本。跑批时长是硬指标。如果分层之后跑批时间反而变长了那说明分层设计有问题可能是任务依赖太复杂或者中间层数据量太大。我经历过一个项目分层之后跑批时间从 2 小时变成 6 小时排查发现是 DWS 层做了太多不必要的汇总后来精简了汇总维度时间降回 3 小时。查询性能是业务方最关心的。DM 层的查询应该在秒级返回如果业务方查个报表要等几分钟那 DM 层的预计算就没做到位。我一般会要求 DM 层的核心报表查询响应时间在 3 秒以内超过这个时间的要么加索引要么调整预计算逻辑。维护成本是长期指标。如果每次加个指标都要改好几层那说明分层太僵化。好的分层应该是可扩展的加一个新指标只需要在 DM 层加一张表或者在 DWS 层加一个汇总维度不需要动 ODS 和 DWD 层。提示我习惯每半年做一次分层架构的复盘看看哪些层可以合并、哪些表可以下线、哪些流程可以自动化。分层不是一劳永逸的它需要随着业务变化不断调整。8. 我在实际项目中的几点体会做了这么多年数据仓库我最大的体会是分层不是目的而是手段。它的目的是让数据流更清晰、更可控、更高效。如果为了分层而分层把架构搞得无比复杂那就本末倒置了。我见过一个团队ODS、DWD、DWS、DM 四层齐全每层都有几十张表但业务方查个数据要等半小时因为任务依赖太多跑批时间太长。后来他们合并了 DWD 和 DWS减少了中间层跑批时间直接降了一半。所以分层要适度够用就好。另一个体会是文档和规范比技术更重要。分层架构的技术实现并不难难的是让团队所有人都遵守同一套规范。表怎么命名、字段怎么定义、任务怎么调度、权限怎么管控这些都需要写成文档并且严格执行。我一般会把规范做成模板新建表的时候直接套用减少人为错误。最后数据质量是分层架构的生命线。再漂亮的分层如果数据不准那就是一堆垃圾。所以不管分层怎么设计数据质量监控一定要跟上。我一般会在每一层都加校验规则比如 ODS 层校验行数和主键DW 层校验关联率和空值率DM 层校验指标波动。一旦发现异常立即阻断下游避免错误扩散。这套分层方法论我在几个项目里反复打磨过不敢说放之四海而皆准但至少能帮你少踩几个坑。如果你正在设计或重构数据仓库不妨从 ODS、DW、DM 这三层开始先把骨架搭起来再慢慢填充细节。记住分层是为了让数据更好用不是为了让自己更忙。