订阅更新

数据模型评审

数仓建模 20 讲 · 第 10 讲 约 14 分钟读完

本文目录
  1. 一、基础规范(3项)
  2. 二、模型设计(4项)
  3. 三、数据质量(3项)
  4. 评审流程:轻量但有效
  5. 面试怎么答?
  6. 总结

上线容易,下线难。这句话我在数仓开发里体会了无数遍。

image.png

一张表上线之后,下游报表、数据接口、调度任务会在几天之内迅速挂上来。等你发现粒度定错了、命名不规范、字段冗余太多想改的时候,下游已经有5张报表和2个接口依赖它了。改一张表要协调7个人的排期,不如一开始就审好。

我之前接手一个项目时遇到过一张"祖传DWS表":粒度混了"订单"和"订单明细",金额字段聚合出来一直是错的。等发现的时候下游已经有十几张报表在用,花了整整一周才完成数据迁移。从那以后,我在团队里推了模型评审机制。 image.png

这篇文章把我实际在用的评审Checklist完整分享出来,10个必查项,按"基础规范、模型设计、数据质量"三个维度组织,每个都有具体的检查方法。


一、基础规范(3项)

① 命名是否符合规范?

这是最基本的检查,也是最容易不达标的。对照DAY 8的命名公式:

{层级}_{业务域}_{主题}_{描述}_{时间粒度}

✅ dws_trade_user_pay_1d        → 一看就知道是什么
❌ user_pay_summary_new_v2      → 哪一层的?什么粒度?
❌ tmp_wood_test_20250115       → 临时表上了生产?

不符合规范的打回去改。如果团队有建表校验机制(DAY 8讲过),这一步其实是自动化的。

image.png

② 表注释和字段注释是否完整?

这一项的执行标准很简单:没注释 = 不通过。

每张表必须有表级COMMENT说明用途,每个字段必须有COMMENT说明含义、单位、枚举值。

-- ✅ 好的注释
CREATE TABLE dws_trade_user_pay_1d (
    user_id     BIGINT   COMMENT '用户ID',
    pay_amount  DECIMAL  COMMENT '当日支付总金额,单位:元,不含退款',
    pay_cnt     INT      COMMENT '当日支付次数',
    first_pay_time TIMESTAMP COMMENT '当日首次支付时间',
    order_status INT     COMMENT '订单状态:0=待支付,1=已支付,2=已取消,3=已退款',
    dt          STRING   COMMENT '数据日期,格式yyyy-MM-dd'
) COMMENT 'DWS层-交易域-用户维度-支付汇总-天级,粒度:一个用户×一天';

-- ❌ 差的注释
CREATE TABLE dws_trade_user_pay_1d (
    user_id     BIGINT,
    pay_amount  DECIMAL,        -- 什么金额?含退款吗?单位是什么?
    pay_cnt     INT,            -- 支付次数还是下单次数?
    status      INT,            -- 什么的状态?0/1/2分别是什么?
    dt          STRING
);

注释写得好不好直接影响三个月后的可维护性。别觉得"我记得住"——三个月后你自己都不记得了,更别说接手的新人。

image.png

③ 分区设计是否合理?

分区决定了查询性能和数据管理的便利性。检查要点:

  • 日增量/日全量表必须有dt分区,没有例外
  • 数据量大且有明确业务过滤维度的,考虑加二级分区(region、biz_type)
  • 分区不要过深——超过3级分区要有充分理由(小文件问题会加剧)
  • 分区字段的格式要和全局规范一致(dt统一用yyyy-MM-dd格式) image.png
-- 检查分区是否合理的常见问题
-- ❌ 没分区的大表(每次全表扫描)
CREATE TABLE fact_event_log (...);  -- 每天5000万行,没分区

-- ❌ 过度分区(三级分区,大量小文件)
PARTITIONED BY (dt STRING, hour STRING, minute STRING);

-- ✅ 合理分区
PARTITIONED BY (dt STRING);                    -- 大多数场景足够
PARTITIONED BY (dt STRING, region STRING);     -- 出海业务按地区分区

二、模型设计(4项)

④ 粒度是否明确且唯一?

这是评审中最重要的一项。一张表的粒度必须能一句话说清楚。

✅ "一行 = 一个用户 × 一天的支付汇总"
✅ "一行 = 一笔订单 × 一件商品的明细"
❌ "嗯...大概是订单相关的汇总数据"
❌ "一行有时候代表订单,有时候代表用户..."

image.png

检查方法:

-- 验证粒度唯一性:表的业务主键不应该有重复
-- 如果粒度是"一个用户×一天",那 (user_id, dt) 应该唯一
SELECT user_id, dt, COUNT(*) AS cnt
FROM dws_trade_user_pay_1d
GROUP BY user_id, dt
HAVING cnt > 1;

-- 如果有结果返回,说明粒度定义和实际数据不一致

粒度混乱是数仓最严重的设计问题。一旦上线后被发现,修复成本极高,因为所有下游的聚合逻辑都可能是错的。

⑤ 是否有重复建设?

新建表之前必须先问一个问题:有没有已存在的表能满足这个需求?

很多时候答案是"有的,只是不知道"。命名不规范、缺乏元数据管理的团队尤其容易出现重复建设。

我曾经在团队里发现3个人各自建了一张用户画像宽表——dws_user_profile、dws_user_portrait、dws_user_tags_wide——字段重叠了70%,每天各跑一遍,浪费了3份计算资源。

image.png

检查方法:

-- 在Hive Metastore里搜索相似表名
SHOW TABLES LIKE '*user*profile*';
SHOW TABLES LIKE '*user*portrait*';
SHOW TABLES LIKE '*user*wide*';

-- 或者用元数据系统搜索关键字段
-- 找到包含 user_id + total_pay_amount + user_level 的所有表

不是说不能有多张类似的表,但每张表应该有明确的差异化定位。如果定位重叠,就应该合并或者指定一张为"主表"。

⑥ 上下游关系是否清晰?

一张表不是孤立存在的。评审时必须回答清楚三个问题:

这张表的数据从哪来?(上游依赖)

dws_trade_user_pay_1d 的上游:
├── dwd_trade_order_detail_di    -- 订单明细表
├── dim_user_df                  -- 用户维度表
└── dim_product_df               -- 商品维度表(可选JOIN)

image.png

这张表谁在用?(下游使用方)

dws_trade_user_pay_1d 的下游:
├── ads_daily_revenue_report     -- 每日营收报表
├── ads_user_retention_report    -- 用户留存报表
└── 投放团队BI大屏(直查)        -- Hologres对接

上游延迟了怎么办?

如果dwd_trade_order_detail_di的ETL延迟了2小时,这张DWS表的产出时间也会推迟。SLA怎么设?是等上游跑完再触发,还是有兜底逻辑用昨天的数据先顶上?

这些问题在评审时不想清楚,上线后第一次出故障就是一团乱麻。

⑦ 维度关联是否正确?

如果ETL逻辑里JOIN了维度表,必须检查:

  • 拉链表是否加了时间范围过滤? 不加WHERE is_current = 1或日期范围过滤,行数必膨胀(DAY 7的内容)
  • 维度表主键是否唯一? 先跑一遍DAY 7的唯一性检查SQL
  • 外键有NULL的情况怎么处理? 用LEFT JOIN还是INNER JOIN?NULL的行保留还是丢弃?
-- 评审时运行:检查JOIN后行数是否正常
SELECT
    (SELECT COUNT(*) FROM dwd_trade_order_detail_di WHERE dt = '2025-01-15') AS src_cnt,
    (SELECT COUNT(*) FROM dws_trade_user_pay_1d WHERE dt = '2025-01-15') AS result_cnt;

-- 如果result_cnt > src_cnt,可能存在数据膨胀
-- 如果result_cnt << src_cnt,可能外键NULL导致INNER JOIN丢失了大量数据

三、数据质量(3项)

⑧ 有没有数据质量校验?

上线的表必须配套数据质量检查,至少包括三类: image.png

主键唯一性检查:

-- 粒度是 user_id × dt,主键不能重复
SELECT user_id, dt, COUNT(*) AS cnt
FROM dws_trade_user_pay_1d
WHERE dt = '${bizdate}'
GROUP BY user_id, dt
HAVING cnt > 1;
-- 如果有结果,说明ETL有Bug

行数波动检查:

-- 和前一天对比,波动超过20%需要告警
WITH today AS (
    SELECT COUNT(*) AS cnt FROM dws_xxx WHERE dt = '${bizdate}'
),
yesterday AS (
    SELECT COUNT(*) AS cnt FROM dws_xxx WHERE dt = DATE_SUB('${bizdate}', 1)
)
SELECT
    today.cnt AS today_cnt,
    yesterday.cnt AS yesterday_cnt,
    ROUND((today.cnt - yesterday.cnt) * 100.0 / yesterday.cnt, 2) AS change_rate
FROM today, yesterday;
-- change_rate 超过 ±20% 就告警

关键字段NULL率检查:

-- 核心指标字段不应该有大量NULL
SELECT
    COUNT(*) AS total,
    SUM(CASE WHEN pay_amount IS NULL THEN 1 ELSE 0 END) AS null_cnt,
    ROUND(SUM(CASE WHEN pay_amount IS NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS null_rate
FROM dws_trade_user_pay_1d
WHERE dt = '${bizdate}';
-- null_rate 超过 5% 需要排查

这些检查要做成调度任务里的自动化卡点,不能靠人工抽查。

⑨ 历史数据是否回刷验证?

新表不能只跑当天数据就上线。至少要做:

image.png

  • 回刷7天数据,验证ETL逻辑在不同日期下都能正确运行
  • 和已有报表/源表交叉验证,核心指标数值是否一致
  • 检查边界情况:月初/月末、节假日、数据量异常大/小的日期
-- 交叉验证:新表的GMV和已有报表的GMV是否一致
SELECT
    a.dt,
    a.total_pay_amount AS new_table_gmv,
    b.gmv_paid AS old_report_gmv,
    ABS(a.total_pay_amount - b.gmv_paid) AS diff
FROM (
    SELECT dt, SUM(pay_amount) AS total_pay_amount
    FROM dws_trade_user_pay_1d
    GROUP BY dt
) a
JOIN ads_trade_daily_report b ON a.dt = b.dt
WHERE a.dt >= DATE_SUB(CURRENT_DATE, 7)
ORDER BY a.dt;
-- diff 应该为0或极小的精度差异

回刷验证虽然花时间(可能要跑1-2小时),但能避免上线后发现数据错误再紧急修复。

⑩ 异常情况有没有兜底?

Murphy定律在数仓里表现得淋漓尽致——能出错的一定会出错。评审时需要考虑:

  • 上游表没数据时:下游是产出空表,还是用前一天的数据补充?
  • 上游数据延迟时:调度依赖怎么设?强依赖等到跑完,还是弱依赖超时后跳过?
  • 数据量突增时:如果某天活动导致数据量翻10倍,存储和计算资源能不能扛住?Executor内存够不够?
  • 重跑是否幂等:如果任务失败重跑了两次,结果是否一致?是否会产生重复数据?
-- 幂等设计:使用 INSERT OVERWRITE 而不是 INSERT INTO
-- 这样重跑不会产生重复数据
INSERT OVERWRITE TABLE dws_trade_user_pay_1d PARTITION (dt = '${bizdate}')
SELECT ...

评审流程:轻量但有效

评审不需要搞得很重——开两小时的评审会、写二十页的设计文档、请五个人签字——那是大公司的做法。3-5人的数据团队,流程越轻越好。 image.png

我的做法是四步走:

1. 开发者自检(10分钟)
   → 对照上面10项Checklist逐一检查,用✅/❌标记

2. Peer Review(15分钟)
   → 找一个同事过一遍设计文档和ETL代码
   → 重点看粒度定义、JOIN逻辑、命名规范

3. 上线验证(1-2小时)
   → 回刷7天数据 + 和已有报表对数 + 质量校验通过

4. 文档归档(5分钟)
   → 表信息录入元数据系统(或更新飞书文档)
   → 包括:表用途、粒度说明、上下游依赖、负责人

整个流程加起来不到2小时,但能避免上线后花一周修问题。花2小时预防 vs 花一周补救,怎么算都划算。

我在团队推行评审机制之后,线上数据质量问题减少了大概一半。更重要的是团队养成了"上线前多想一步"的习惯。现在大家自己建表都会主动对照Checklist检查,不用我催。

image.png


面试怎么答?

高分回答

「我们团队有一套模型评审机制,是我推动建立的。核心是一个10项Checklist,覆盖三个维度:基础规范(命名、注释、分区)、模型设计(粒度定义、避免重复建设、上下游关系、维度关联)、数据质量(主键唯一、回刷验证、异常兜底)。每张新表上线前需要开发者自检 + 一个同事Peer Review,回刷至少7天数据、和已有报表对数通过才能上线。推行之后线上数据质量问题减少了约50%。」

这个回答好在哪里?它展现的不只是技术能力,更是团队协作和规范化推动能力。这是中高级岗位面试官特别看重的维度——你不仅自己能做好,还能带动团队一起做好。 image.png

追问防线

追问1:Peer Review会不会影响开发效率?

15分钟的Review看起来是"额外成本",但和线上出了问题后花半天排查、花一周修数据的成本比起来,简直太划算了。而且Review本身也是团队知识共享的机会——Reviewer能了解其他人在做什么,开发者也能得到不同视角的反馈。

实际上推行之后,大家反馈是开发效率反而提高了——因为Review过程中经常能提前发现问题,避免了上线后返工。

追问2:评审发现的问题怎么跟踪?

我们用飞书的任务列表来跟踪。Review时发现的问题记成TODO,标注严重程度(P0必须修才能上线,P1上线后一周内修,P2排期修)。每周站会过一遍待修复的P1/P2问题。

追问3:小团队有必要搞这些规范吗?

越小的团队越需要。大公司有完善的数据平台、元数据系统、自动化测试来兜底;小团队没有这些基础设施,全靠人来保证质量。一个3人团队如果不做Review,一个人犯的错误可能要另外两个人花双倍时间来修。

当然,小团队的规范要轻量——一页纸Checklist就够了,不要搞繁重的流程。关键是养成习惯,不是搞形式。


总结

  • 上线容易下线难,一开始就要审好,避免下游依赖后改不动
  • 10项Checklist分三个维度:
    • 基础规范:命名、注释、分区
    • 模型设计:粒度唯一、避免重复建设、上下游清晰、维度关联正确
    • 数据质量:主键唯一校验、回刷验证、异常兜底
  • 流程要轻量但有效:自检10分钟 → Peer Review 15分钟 → 回刷验证 → 文档归档
  • 面试加分关键:不只是"我会做",更是"我推动了团队规范化"

数仓建模系列 DAY 1-10 到这里全部更新完了。从分层架构、维度建模、SCD拉链表、事实表类型、宽表设计、指标口径、数据膨胀、命名规范、OneID到模型评审,覆盖了数仓建模面试的核心考点。 image.png

这10篇的知识是串联在一起的——面试官经常从一个点追问到另一个点。建议把这10篇串起来复习,形成自己的知识体系。面试时能把不同话题之间的关系讲清楚(比如"宽表设计需要注意数据膨胀问题,而膨胀通常是因为维度表的缓慢变化维没处理好"),这才是真正的高手。

image.png

刷题进度保存在当前浏览器