news 2026/11/2 3:29:39

数据仓库面试题工程手册:四层模型、拉链表与维度建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据仓库面试题工程手册:四层模型、拉链表与维度建模

简介:《数据仓库工程师面试题》是一份面向数据仓库工程师求职者与初级数仓从业者的知识梳理文档,围绕面试常见考点组织内容,可用于系统复习与查漏补缺。文档共1个docx文件,压缩包约31KB,篇幅精炼,便于在手机或电脑上直接查阅。内容从数据仓库的定义及主题性、集成性、稳定性、时变性四大特点切入,延伸至ETL的抽取、转换、加载流程,执行过程日志、错误日志、总体日志的分类与告警发送机制,并梳理STG、ODS、DW、Presentation四层模型的职责边界、审计列设计、CDC策略与开发步骤,同时涉及数据清洗、规范化、维度表和事实表提交等建模环节,对STG层减轻源系统压力、支持重跑等作用也有说明。已有1712人学习,适合需要快速建立数仓知识框架、对照面试题查漏补缺的读者。

1. 一份 docx 面试题,为什么值得当成工程手册来拆

面试官问拉链表怎么实现,很多人能把 start_date、end_date 讲清楚,真让写关链那一段 SQL 就卡壳了。这份《数据仓库工程师面试题》docx 就是冲着这种断层来的:它把数据仓库知识按主题性、集成性、稳定性、时变性四条特点铺开,再一路讲到 ETL 四步、STG/ODS/DW/DM 四层模型、缓慢变化维与拉链表、星型雪花模型,最后落到事实表和维度表的类型划分。它适合两类人——准备面试但只会背概念的,以及已经在一线写 ETL、但建模理论一直是黑匣子的。下面按「能跑起来」的顺序拆一遍,每一段都尽量落到建表语句、SQL 和参数上,而不是停在名词解释。

2. 四层模型落地:STG/ODS/DW/DM 每层建表该留哪些列

分层这件事,讲概念五分钟就完了,真正决定项目成败的是每层表里留哪些列、字段类型怎么定、加载频率多高。原文把四层写成 STG、ODS、DW、DM(展示层),并且明确 STG 与源系统模型一致、不做转换,ODS 保留全量并加逻辑删除标记和增量时间戳,DW 做清洗规范化和维度建模,DM 做聚合与宽表。这一节把这四句话翻译成能直接执行的建表语句。

2.1 STG 层到底解决什么问题,能不能直接抽进 ODS

我见过不少人为了省事,把源库数据一把抽进 ODS,跑了大半年也没出事,于是结论是 STG 这一层是多余的。这种判断通常要等到第一次「源系统在抽取窗口里做了批量订正」才会翻车:没有 STG 的中间落地,重跑就意味着再压一次源库,业务方一个电话打过来,说报表库白天被抽数抽慢了。

STG 的四个作用里,最容易被低估的是「支持重跑」。它的定位是一次抽取的着陆区,只保留 7 天到 1 个月,过期就清。这意味着你可以放心地在 ODS 或 DW 层反复重算,而不必再碰源系统。第二个作用是减轻源系统压力,尤其是那种只给半小时抽取窗口的核心业务库,STG 是唯一能让你在窗口内做完抽取、窗口外慢慢加工的地方。第三是数据问题跟踪,STG 保留了源系统当日原样的快照,下游算错了可以回溯到「抽进来的时候长什么样」。第四是数据质量检查,很多完整性、唯一性校验放在 STG 做完再进 ODS,脏数据不会污染历史存档。

这里有个关键取舍:STG 不做任何数据转换,包括不去重、不裁剪、不改类型。原文说得很直接——「stg 层不做数据转换」。一旦你在这里加了业务逻辑,后面出问题就无法判断是源数据脏还是转换写错了。唯一允许加的是审计列。

2.2 STG 建表:审计列、字段长度和加载方式的写法

下面这段是 Hive/Spark SQL 风格的 STG 建表模板,源表假设叫 order_main,取自订单业务库。

CREATE TABLE IF NOT EXISTS stg_order_main ( order_id VARCHAR(60) COMMENT '订单号,源 varchar(32) 已扩容', cust_id VARCHAR(60) COMMENT '客户号', order_status VARCHAR(30) COMMENT '订单状态,源值原样保留', pay_amount DECIMAL(20,4) COMMENT '支付金额,数值统一精度', create_time VARCHAR(30) COMMENT '源创建时间,字符串原样落地', update_time VARCHAR(30) COMMENT '源更新时间', -- 以下为审计列,每层都要有 src_sys_cd VARCHAR(20) COMMENT '源系统编码,如 CRM/OMS', src_tbl_name VARCHAR(60) COMMENT '源表名', load_type VARCHAR(4) COMMENT 'I=增量 F=全量', etl_batch_id VARCHAR(40) COMMENT '批次号,格式 yyyyMMddHHmmss', load_time TIMESTAMP COMMENT '落地时间' ) PARTITIONED BY (load_date VARCHAR(10) COMMENT '分区日期 yyyy-MM-dd') STORED AS ORC;

逻辑说明:这张表只做三件事——把源字段照搬、把类型统一到少数几种、把审计列补齐。分区键用 load_date,是为了让「保留 7 天」变成一句ALTER TABLE ... DROP PARTITION,而不是一条范围删除的大事务。

参数说明几个具体规则。第一,字段长度一律按源长度的 1.5 到 3 倍扩充,源varchar(32)落成VARCHAR(60),原因是上游改字段长度往往不通知下游,留余量比事后改表便宜得多。第二,数值统一DECIMAL(20,4),不要用 FLOAT/DOUBLE,金额一进浮点就等着对不上账。第三,时间统一落成字符串yyyy-MM-dd HH:mm:ss或 TIMESTAMP,二选一,全项目不许混用,混用是后面增量抽取最大的坑之一。第四,load_type 一定要有,否则重跑时无法区分这批数据是全量覆盖还是增量追加。

2.3 ODS 层:逻辑删除标记和增量时间戳怎么加

ODS 和 STG 的模型一致,但多了两样东西:逻辑删除标记和增量时间戳。原文强调的原因是源系统可能做物理删除,甚至在有逻辑删除标记的情况下,后台运维也会手工 DELETE。如果 ODS 只做追加,下游看到的是「这条记录消失了」,而事实是「它被删了」,这两个语义在报表上完全不同。

CREATE TABLE IF NOT EXISTS ods_order_main ( order_id VARCHAR(60), cust_id VARCHAR(60), order_status VARCHAR(30), pay_amount DECIMAL(20,4), create_time VARCHAR(30), update_time VARCHAR(30), is_deleted TINYINT COMMENT '0=有效 1=逻辑删除', src_update_time TIMESTAMP COMMENT '源端更新时间,用于增量比对', etl_batch_id VARCHAR(40), dw_insert_time TIMESTAMP COMMENT '本行首次进入数仓时间', dw_update_time TIMESTAMP COMMENT '本行最近一次变更时间' ) PARTITIONED BY (dt VARCHAR(10)) STORED AS ORC;

物理删除的检测靠一次全外连接比对,把「目标表有、源表没有」的那部分打上删除标记:

INSERT OVERWRITE TABLE ods_order_main PARTITION (dt = '${bizdate}') SELECT COALESCE(s.order_id, t.order_id) AS order_id, COALESCE(s.cust_id, t.cust_id) AS cust_id, COALESCE(s.order_status, t.order_status) AS order_status, COALESCE(s.pay_amount, t.pay_amount) AS pay_amount, COALESCE(s.create_time, t.create_time) AS create_time, COALESCE(s.update_time, t.update_time) AS update_time, CASE WHEN s.order_id IS NULL THEN 1 ELSE 0 END AS is_deleted, CAST(COALESCE(s.update_time, t.src_update_time) AS TIMESTAMP) AS src_update_time, '${batch_id}' AS etl_batch_id, COALESCE(t.dw_insert_time, CURRENT_TIMESTAMP) AS dw_insert_time, CURRENT_TIMESTAMP AS dw_update_time FROM stg_order_main s FULL OUTER JOIN ods_order_main t ON s.order_id = t.order_id AND t.dt = '${yesterday}' WHERE s.load_date = '${bizdate}';

逻辑说明:以源表为主、目标表为辅做全外连接,源表缺失的行就是被物理删除的行,标记 is_deleted=1 但保留记录,保证下游按任意历史日期查询时能还原当时的全量视图。dw_insert_time 用 COALESCE 保留首次写入时间,这是后面做拉链表和审计的共同基础。

参数说明:${bizdate}是调度系统传入的业务日期,不是系统当前日期,两者在跨零点跑批时经常差一天;${yesterday}用于关联上一分区,做变更比对;分区粒度按天,如果源系统一天多次变更且业务要求 T+0 可见,就要改成小时分区,代价是分区数量涨 24 倍,元数据压力要提前评估。

2.4 DW 与 DM:维度建模和聚合的分界线在哪

DW 层是清洗、规范化、提交一致维度和事实的地方,DM 层是按业务需求做聚合或宽表。这两层的边界模糊是很多项目的通病,表现为 DM 层里塞了一堆明细关联逻辑,或者 DW 层直接出报表口径。

判断标准很简单:DW 层回答「这个业务实体长什么样」,DM 层回答「这个指标等于多少」。凡是带 SUM、COUNT、同比环比的,一律放 DM;凡是带代理键、维度属性、缓慢变化处理的,一律放 DW。这样切的好处是口径变更只改 DM,模型变更只改 DW,互不牵连。

原文里 DM 层建聚合表给了三条路径:增量加载聚合表、聚合导航(由报表工具按请求自动路由到基础事实表)、物化视图定时刷新。我一般选第一条,原因是可控——聚合表的口径写在 ETL 里,能测试、能重跑、能对账;聚合导航把口径交给工具,出了问题排查成本高;物化视图刷新失败时错误信息往往很含糊。如果查询引擎对物化视图的自动改写支持得好,可以只在超大宽表上用它兜底。

代理键这一环单独说一句:DW 层的维度表必须有代理键,且必须有代理键为 -1 的默认行。原文提到「首次加载的时候要有一条代理键为 -1 的默认记录,为了防止事实表查找不到代理键」。这条经验值千金,事实表关联维度时如果用了内连接,坏数据会被静默丢掉,指标就少了,而且没人发现。

3. 拉链表与缓慢变化维:Type1/2/3 的选择和关链开链 SQL

这一章是面试高频区,也是实操最容易写错的地方。缓慢变化维的三种处理方式本身不难,难在选型——Type2 用得不对,一张十亿行的用户表三年能膨胀到几十亿行,查询性能直接塌方。

3.1 SCD 三种类型的取舍:什么时候 Type2 会把表撑爆

Type1 是直接覆盖,旧值不留痕,适合「改错了要能纠正」的属性,比如录入错误的手机号。Type2 是增加新行,旧行置为失效并写结束时间,新行写开始时间,适合「需要看历史某时点状态」的属性,比如客户所属销售区域、订单状态。Type3 是增加新列,只保留上一个值,适合变化频率低但确实需要对比的场景。

选型的核心变量是变化频率乘上数据量。原文给了快速变化维的三档处理:小表用 Type2;大表用 Type3,只留 previous_value 和 current_value;超大表且只有一部分字段频繁变化,就把频繁变化的字段拆出去单独成维,和事实表另建关联。这一条我完全认同,实际项目里最常踩的坑就是「无脑 Type2」——一个每天变一次的价格维度,用 Type2 存一年就是 365 倍行数。

还有一个细节:Type2 的结束时间和开始时间不要用「上一条记录的结束时间 +1 秒」这种写法。原文提到begintime设为上一记录的endtime+1,思路可以,但在秒级精度下容易出现同一秒内多条变更的记录互相覆盖。稳妥做法是整条链用半开区间[start_date, end_date),失效行 end_date 直接写变更时刻,有效行 end_date 写9999-12-31,查询用start_date <= t AND t < end_date,边界判断永远只有一种写法。

3.2 拉链表的两张表结构和关链、开链 SQL

拉链表本质是 Type2 在超大事实明细上的变体。原文给的结构是主表dw_order_his带start_date和end_date,另建一张ods_order_update存当日变更数据(含 insert 和 update)。

-- 1) 当日变更数据:源表与拉链表按主键比对,只留真正变化的 INSERT OVERWRITE TABLE ods_order_update PARTITION (dt = '${bizdate}') SELECT s.order_id, s.order_status, s.pay_amount, s.update_time FROM ods_order_main s LEFT JOIN dw_order_his h ON s.order_id = h.order_id AND h.end_date = '9999-12-31' WHERE s.dt = '${bizdate}' AND s.is_deleted = 0 AND (h.order_id IS NULL OR s.order_status <> h.order_status OR s.pay_amount <> h.pay_amount); -- 2) 关链:变更数据的交集部分,把旧链的 end_date 封口 INSERT OVERWRITE TABLE dw_order_his PARTITION (dt = '${bizdate}') SELECT h.order_id, h.order_status, h.pay_amount, h.start_date, u.update_time AS end_date FROM dw_order_his h JOIN ods_order_update u ON h.order_id = u.order_id WHERE h.end_date = '9999-12-31' UNION ALL -- 3) 开链:新增数据以 9999-12-31 结尾挂上去 SELECT u.order_id, u.order_status, u.pay_amount, u.update_time AS start_date, '9999-12-31' AS end_date FROM ods_order_update u;

逻辑说明:第一步是全字段(除两个日期字段)比对,把源表和当前有效链不一致的记录捞出来,这是「真增量」,不是「源表 update_time 大于昨天」的伪增量。第二步对交集做关链,把旧行的 end_date 改成变更时刻。第三步把变更数据作为新行插入,start_date 取变更时刻,end_date 写 9999-12-31。第二步里的h.end_date = '9999-12-31'是必须的,它保证同一条链只被关一次,否则重跑会把已经封口的历史行又改一遍,历史就彻底乱了。

参数说明与写法要求:整个 INSERT OVERWRITE 重写当日分区,不做 UPDATE,这是数仓里最常见的做法,因为大多数 MPP 和 Hive 类引擎的 UPDATE 代价高、且不可重入;9999-12-31作为哨兵值要在全项目统一,不要有的地方写2999-12-31;变更判定的字段列表必须显式列出,不能用SELECT *比对,否则源表加字段会静默改变增量口径。

3.3 全量对比找真增量:为什么不能只靠 update_time

原始资料里明确点出「一般情况下,业务系统没有或者部分有时间戳」。这是增量抽取最真实的困境。有 update_time 的系统,也要防三种情况:一是批量订正时用UPDATE ... SET status=status这类空更新,时间戳变了但值没变,增量里全是噪音;二是主从延迟导致时间戳落后于实际业务时间,抽取窗口切在延迟区间就会漏数;三是时区不统一,源库按本地时间写,数仓按 UTC 算,跨零点必错。

所以只要表不是特别大,我倾向做全字段比对。代价是一次全表 join,收益是增量绝对准。用户表这种十亿行、每天变两百万的,全字段比对的成本可以通过只比对变化概率高的字段来降低,比如联系方式、状态、区域,而不是比对全部五十个字段。

原文还给了一条增量抽样的经典做法:每次抽取前先查 ODS 里该表的最大时间,然后去源系统取大于这个时间的所有记录。这个思路成立的前提是时间戳单调且无延迟,用的时候记得用左闭右开区间> last_max AND <= now,两头都用闭区间就会重复抽取边界数据,重复数据在事实表里就是指标翻倍。

3.4 拉链表的查询写法和性能手段

拉链表查询的时间点条件只有一种正确写法:

SELECT order_id, order_status, pay_amount FROM dw_order_his WHERE start_date <= '2024-06-30 23:59:59' AND '2024-06-30 23:59:59' < end_date;

逻辑说明:半开区间保证任一时刻有且只有一条记录命中,不会出现在变更瞬间同时命中新旧两行的情况。

性能上原文给了两条:对 start_date、end_date 建索引;保留部分历史数据,比如对外只暴露近三个月的拉链表。这两条在 MPP 里依然有效,但索引在列式存储上收益有限,更有效的是分区。我一般按start_date的年月做分区,把「查某个时间点」变成「先定位分区再过滤」,扫描量能降一到两个数量级。如果业务确实只查近三个月,就再建一张dw_order_his_3m的短链视图或物化表,长链只留给合规审计用。注意,短链表不能反向同步回长链,否则关链逻辑会重复执行。

4. 事实表与维度表:四种事实粒度与加载顺序的依赖约束

维度建模这一块,面试问得最多的是「星型还是雪花」和「事实表有几种」,但真正影响上线的是加载顺序和代理键一致性。

4.1 四种事实表的粒度差异和建表示例

原文把事实表分成四类,我按自己的理解补上判别方法。

类型粒度定义典型场景聚合方式常见误用
事务粒度事实表一次业务事件一行订单、支付流水可加,任意维度 SUM把不同粒度事件塞进同一张表
周期快照事实表固定时间间隔一行账户日终余额、月末库存半可加,跨时间不能直接 SUM间隔不固定,混入日/月两种粒度
累积快照事实表一个实体从创建到当前一行订单履约、工单流转非可加,看当前状态原文译作「聚合快照」,与聚合表混淆
无事实事实表事件发生但无数值页面访问、促销参与只做 COUNT硬造一个值为 1 的度量字段

这里要点出一个原文的表述坑:Non-Additive Fact被译成「聚合快照事实表」,而它对应的英文是 Accumulating Snapshot Fact,标准译法是「累积快照」。累积的意思是同一行被反复更新,从下单到发货到签收,多个时间戳落在同一行上。如果按「聚合」去理解,很容易做成一张按天汇总的表,那就完全是另一回事了。

CREATE TABLE dwd_order_accum ( order_id VARCHAR(60) COMMENT '订单号,业务主键', cust_sk BIGINT COMMENT '客户代理键', prod_sk BIGINT COMMENT '产品代理键', order_date_sk INT COMMENT '下单日期代理键', pay_date_sk INT COMMENT '支付日期代理键,未支付为 -1', ship_date_sk INT COMMENT '发货日期代理键,未发货为 -1', order_amount DECIMAL(20,4) COMMENT '订单金额', order_status VARCHAR(30), etl_batch_id VARCHAR(40) ) PARTITIONED BY (dt VARCHAR(10));

逻辑说明:累积快照的主键是订单号,每来一次状态变更就更新同一行对应的时间戳和状态字段,行数等于订单总数,不随时间增长。未发生的里程碑统一填 -1 代理键,而不是 NULL,原因是 NULL 在关联和分组里的行为各引擎不一致,-1 是确定的。

参数说明:所有代理键用 BIGINT,别用 INT,十亿级维度加上历史版本很容易溢出;金额用 DECIMAL,不用 DOUBLE;分区按天存的是每日全量快照,方便回看任意一天的状态,存储成本靠只保留近 N 天加月末快照来控制。

4.2 维度表的五种分类,面试官真正想问的是什么

原文列了缓慢变化维、快速变化维、大维、迷你维、退化维。这五个词背下来不难,面试官追问的是「你的项目里哪一种真的用上了」。

缓慢变化维是基础,前面已经说过。快速变化维的处理核心是拆,把频繁变化的属性从主维拆出去,形成一张小维表或直接做成事实表的退化字段。大维指客户、产品这种上百万到上千万行、上百个字段的维度,处理手段是精简属性、选对索引、必要时做迷你维。迷你维是把大维里最常查的少数字段抽出来单独成表,查询先命中迷你维再回主维,减少扫描宽度。退化维是订单号这种「存在于事实表里、没有独立维表」的维度,直接留在事实表上用于分组和去重。

一个实操建议:退化维不要为了「模型完整」硬建一张只有一列的表,那纯属给自己增加关联。原文提到父子维要慎用 Type2,因为自引用关系一旦有层级变动,整棵子树都要跟着改,逻辑极难理顺,建议按覆盖方式处理。这个判断在组织架构维度上尤其成立——组织调整一年一次,为了看历史层级而在整棵树上做拉链,维护成本远高于收益。

4.3 加载顺序为什么必须是子维度→维度→桥接→事实

原文给的顺序是:子维度表先加载,然后维度表,然后桥接表,最后事实表。依据是主外键依赖:事实表引用维度代理键,维度表引用子维度,桥接表处理多值维度和层级。

这个顺序在调度系统里要落成显式的依赖配置,不能靠「任务按文件名排」这种玄学。我见过的事故是维度任务因为上游延迟晚跑了二十分钟,事实任务照常启动,结果新订单关联不到当天新增的客户维度,代理键全落到 -1 上,第二天报表的客户维度全是「未知」,查了两小时才发现是调度依赖少画了一条线。

具体做法:每个任务声明输入表和输出表,调度器据此自动生成 DAG,再人为补充那些「表上无外键但逻辑上有依赖」的边。数仓里大量表是不建外键的(建了影响写入性能),所以逻辑依赖必须靠元数据显式声明。校验手段也简单:事实表加载完成后跑一条孤儿键检查,SELECT COUNT(*) FROM fact f LEFT JOIN dim d ON f.cust_sk = d.cust_sk WHERE d.cust_sk IS NULL,不为零就告警。

4.4 星型与雪花:冗余换 join 的这笔账怎么算

星型模型的维度直接连事实表,维度不再往下拆;雪花模型把维度规范化,通过其他维表连到事实表。原文的结论是「在冗余可接受的前提下使用星型模型」,因为星型少一层关联,统计不需要外部 join,效率更高。

这笔账要算清楚:雪花省的是存储,代价是每次查询多一次 join。在列存加压缩的今天,维度表的存储成本几乎可以忽略,一个产品维度规范化后省下的空间可能就几十 MB,而多出来的 join 在亿级事实表上可能让查询慢一倍。所以除了那种超大且层级深、且层级本身需要独立维护的属性(比如商品类目树,类目会单独调整),其余一律星型。

判断雪花该不该拆的一个实用标准:这个子维度是否被多个维度共用、是否有独立的维护流程和负责人。是,就拆;只是「看起来更规范」,就别拆。

5. 避坑排查:ETL 日志、增量抽取和代理键的五个翻车现场

这一章是这份资料里最值钱的部分被翻译成实战之后的样子。原文把 ETL 日志分成执行过程日志、错误日志、总体日志三类,并强调出错要发告警邮件。真正跑过夜批的人都知道,日志设计得好不好,决定了故障时是十分钟定位还是通宵。

5.1 现象:任务失败重跑后,指标翻倍

原因:增量抽取用了>=边界,或者加载方式是 INSERT INTO 而不是分区覆盖。第一次跑成功、写日志失败,调度器判定失败重跑,边界数据又进来一遍。

解决:所有增量加载统一用分区覆盖或按主键 MERGE;时间边界一律左开右闭> last_max AND <= batch_time;把 last_max 存到一张水位表里,和业务数据在同一事务或同一批次更新,避免水位推进了但数据没落。

5.2 现象:报表里客户维度突然大面积显示「未知」

原因:事实表加载早于维度表,或者维度表的代理键生成器在重跑时从当前最大值继续,导致同一业务主键拿到两个代理键。

解决:调度上强依赖维度先于事实;代理键生成不要用「当前最大值 +1」这种写法,改用序列或 row_number 配合业务主键去重;维度表必须有一条 sk=-1 的默认记录兜底。这条默认记录的价值在于让「关联不上」显式暴露成「未知」而不是被内连接静默丢掉。

5.3 现象:拉链表跑完之后,历史查询结果全变了

原因:关链的那段 SQL 少了end_date = '9999-12-31'条件,重跑时把已经封口的历史行又改了一次 end_date;或者用了 UPDATE 而非整分区重写,中途失败留下半截状态。

解决:关链条件必须带有效链过滤;整分区 INSERT OVERWRITE 保证原子性;跑完加一条自检 SQL,检查是否存在同一订单的多条有效链:SELECT order_id, COUNT(*) FROM dw_order_his WHERE end_date = '9999-12-31' GROUP BY order_id HAVING COUNT(*) > 1,结果非空就阻断下游。

5.4 现象:源系统字段长度一改,加载直接报错或者中文被截断

原因:STG 建表时字段长度照抄源系统,或者类型选了 VARCHAR 但没留余量。源端从 32 改到 64,Hive 写入时超长部分被静默截断(部分引擎不报错),下游按客户名关联全部失效。

解决:字段长度按源长度 1.5 到 3 倍扩容,数值统一 DECIMAL(20,4),时间统一字符串或 TIMESTAMP;上线前跑一次长度探测,SELECT MAX(LENGTH(cust_name)) FROM stg_xxx,和表定义比对。这条检查我一般挂在 STG 加载后的质量校验里,成本很低。

5.5 现象:ETL 失败后查不到断点,只能整条链路重跑

原因:只记了总体日志(开始时间、结束时间、成功与否),没有执行过程日志和错误日志。原文说得很清楚,过程日志要记录每一步的起止时间、影响行数,错误日志要记录出错时间、出错模块和错误信息。

解决:建一张 ETL 运行日志表,字段至少包含 batch_id、job_name、step_name、start_time、end_time、row_count、status、err_msg;每个步骤前后各写一条;错误日志单独一张表,存完整堆栈。告警走邮件或企业消息通道,内容带上 batch_id 和出错步骤,方便直接定位到分区。这张表本身也要定期清理,否则三年后它自己会变成最大的表。

6. 用追问反推掌握度:一套 12 问的自测表和三条硬习惯

把这份 docx 从头读一遍只要四十分钟,但读完不等于会用。我自己的检验方式是把每个知识点改写成「追问形式」,然后要求自己不看资料答出来,答不出来的回去补。下面这张表是压缩版,左边是原始问题,右边是面试官真正想听的落点。

原始问题追问落点答不出来的典型表现
什么是数据仓库四条特点里哪条和 OLTP 冲突最大只背定义,说不出稳定性与更新的矛盾
ETL 是什么清洗放在 ODS 前还是 DW 前不知道脏数据该在哪层拦
四层模型STG 保留多久,为什么把 STG 当永久落地层
增量抽取源表没有时间戳怎么办只会说「用时间戳」
缓慢变化维十亿行表为什么不用 Type2三个类型背得出,选型说不出
拉链表关链条件为什么必须带有效链过滤写不出那段 SQL
拉链表优化除了索引还有什么手段想不起分区裁剪和短链表
星型与雪花什么情况下雪花是对的只会说星型效率高
事实表类型累积快照和周期快照差在哪把累积快照说成聚合表
维度表类型退化维为什么不需要维表五个名词对不上场景
加载顺序数仓不建外键,依赖怎么保证说不出元数据声明加孤儿键检查
ETL 日志三类日志分别解决什么问题只记得「要发邮件」

三条硬习惯,是我从几次翻车里攒下来的。

第一条,任何增量逻辑上线前,先构造三条测试数据:一条新增、一条修改、一条物理删除,跑完核对拉链表的行数和 end_date。这一步五分钟,能挡掉后面几个小时的对账。

第二条,每个事实表加载完立刻跑孤儿键检查和行数波动检查。行数波动超过均值上下 30% 就告警,这个阈值不精确,但足够抓住「上游少给了一个分区」这类事故,比等到业务方发现指标腰斩要早一天。

第三条,所有涉及历史的表都写一对自检 SQL:有效链唯一性、时间区间无重叠。把它们固化成调度里的独立节点,失败就阻断下游,而不是写成文档放在 wiki 里等人去看。

最后说个我自己的教训。早些年做一张订单拉链表,关链时为了「性能」把end_date = '9999-12-31'换成了end_date > '2020-01-01',测试环境数据少,跑得挺顺。上线三个月后做历史回溯,发现有些订单在同一时点有两条有效记录,查了两天才定位到是那次「优化」。从那以后我每次改拉链逻辑,都强制走一遍「造三条数据、跑一遍、验两条自检 SQL」的流程,不管改动多小。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 3:29:26

Git提交遗漏文件修复指南:amend与补救方案详解

1. 问题场景还原刚完成代码修改的你&#xff0c;熟练地执行了git add和git commit操作&#xff0c;甚至已经git push到了远程仓库。突然发现漏提交了一个关键文件——可能是配置文件、测试用例或是新添加的依赖项。这时候命令行就像个无情的裁判&#xff0c;用红色错误提示宣告…

作者头像 李华
网站建设 2026/11/2 3:29:14

工业4.0时代UWB毫米级定位技术解析与应用

1. 制造业室内定位的技术痛点与毫米级突破在工业4.0的浪潮中&#xff0c;工厂自动化面临着一个看似微小却影响深远的"最后一厘米"问题——当AGV小车需要精准停靠装配工位时&#xff0c;当机械臂要抓取传送带上的零件时&#xff0c;传统定位技术5cm的误差就可能导致整…

作者头像 李华
网站建设 2026/11/2 3:28:35

MiMo V2.6双登顶背后:效率竞赛下的单位智能成本与模型选型指南

最近圈子里聊大模型的方式变了。以前大家见面还会问“你用的多大参数”“榜单第几名”&#xff0c;现在更常聊的是“部署成本”“推理吞吐”“每万 token 花多少钱”。这不仅仅是话题口味的变化&#xff0c;大模型竞赛的重心确实在从“堆参数”转向“抠效率”。MiMo V2.6 双登顶…

作者头像 李华
网站建设 2026/11/2 3:28:13

Redisson WatchDog机制解析与分布式锁自动续期

1. Redisson的WatchDog机制解析在分布式系统中&#xff0c;锁的管理是个棘手的问题。传统Redis分布式锁方案最大的痛点在于&#xff1a;当业务执行时间超过锁的过期时间时&#xff0c;会导致锁提前释放&#xff0c;进而引发数据一致性问题。Redisson的WatchDog&#xff08;看门…

作者头像 李华
网站建设 2026/11/2 3:28:11

电力系统潮流计算模块的伪代码设计与实现

1. 项目概述"伪代码&#xff1a;调用潮流计算模块"这个标题涉及电力系统分析领域的一个重要技术环节。潮流计算(Power Flow Calculation)是电力系统稳态分析的基础&#xff0c;用于计算电力网络中各节点的电压幅值、相角以及支路功率分布等关键参数。在实际工程应用中…

作者头像 李华
网站建设 2026/11/2 3:28:06

Mamba模型被拒怎么办:核心机制、评审疑点与方案打磨

开头先说个真实感受&#xff1a;这两个月我被问到最多的问题就是“Mamba模型到底行不行”&#xff0c;而且问法高度雷同——“我投出去被拒了”“方案评审被否了”“组里资深同事一句‘这东西不靠谱’就给我打回来了”。Mamba模型被拒&#xff0c;几乎成了近两年做序列建模的人…

作者头像 李华