简介:一份面向数据仓库初学者与 ETL 开发者的 PPT 课件,围绕 ETL 抽取、转换、加载全流程展开,系统梳理 ETL 定义、实施前提、设计原则、模式比较及常见问题处置,既可用于个人学习,也可作为技术培训或面试准备的参考资料。课件重点对比了异构与同构两种 ETL 模式在性能、容错、维护成本与开发灵活性上的差异,并详细说明数据中转区预处理、主动拉取、流程化配置、数据质量保障等关键原则;同时剖析了抽取时间区间与源系统生产时段重叠、系统停机窗口、快照机制等高频踩坑问题,给出抽取失败重抽、装载回滚、必要时恢复系统状态等错误处理方案。此外,资源对 ETL 过程中容易忽视的源数据变动和重复装载问题也做了单独提示,帮助读者在实际项目中规避典型风险。资源为单个压缩包,共 1 个 ppt 文件,大小约 932KB,以图文形式呈现 ETL 数据流图与过程分析,结构紧凑、便于快速翻阅。已有 355 人学习查看,适合数据仓库入门者、ETL 工程师及准备面试的读者。
1. ETL不是搬运工,是数据仓库的守门员
数据仓库里跑出来的报表,一旦业务方反馈“数字对不上”,大部分问题的源头最后都指向同一个环节:ETL。抽取窗口多拉了一条过期记录、清洗规则漏掉了某个空值分支、加载时主键冲突导致重复数据——这些问题在日常ETL流程里远比 SQL 语法错误更难发现。ETL(Extract、Transform、Load)的职责,是把日常业务操作的数据转化为针对数据仓库而存储的决策支持型数据,同时保证正确性、一致性、完整性、有效性、可获取性。这份方案从 ETL 定义、模式选择、过程分解到数据流图,完整覆盖了数据仓库核心组件的设计路径,适合数据开发工程师、数仓架构师以及正在备考 ETL 面试的从业者对照项目实践来读。
2. 异构与同构:ETL架构模式选型的边界
2.1 两种模式在管道形状上的本质区别
同构和异构很容易被误解成数据库层面的概念,实际上这里说的是 ETL 管道的拓扑。同构模式(Synchronous)下,源表和目标表之间是直接映射,抽取和装载一次完成,没有中间文件。异构模式(Asynchronous)不一样,它在源和目标之间插入了一个中间过渡层,数据先从源表导出成固定结构的文本文件,再由另一套程序把文件装入目标库。
管道形状看起来只差一个文件落地,但开发、维护和排障方式都会随之改变。异构模式下源和目标的数据接口分离,只需要定义好中间文本文件的结构,源端开发和目标端开发就能并行推进,各自完成后装配即可;同构模式的开发人员必须同时熟悉源结构和目标结构,因为映射关系绑定在具体表字段上。还有一个存储过程的差异:异构模式的导出过程既可以在源库执行,也可以放到目标库执行,而且导出源可以是表、视图或临时表;同构模式只有一步导出装载,简化了设计和测试,但灵活性也相应下降。
实际项目中,模式选型经常在架构评审阶段争论很久。我的建议是先把“源和目标是否同一个数据中心、数据量是否大到跨网络传输不划算、源库字段类型是否复杂”这三个问题回答完,再进入细节比较。
2.2 开发、维护与错误处理的七个比较维度
方案里给了七个维度,汇总成下面这张表:
| 比较维度 | 异构(Asynchronous) | 同构(Synchronous) |
|---|---|---|
| 数据处理性能 | 传输文件快于数据库直连,处理时间更短 | 直接映射,无中间文件 |
| 数据转换步骤 | 导出文本 + 装载文件,两步完成 | 一个步骤完成导出和装载 |
| 开发方式 | 源和目标接口分离,可并行开发 | 开发人员需同时熟悉两端结构 |
| 结构变更影响 | 文本结构不变则影响很小 | 结构变化需同步修改映射 |
| 查错难度 | 中间文件是快照,问题位置快速定位 | 源数据变化导致问题定位难 |
| 异常恢复 | 回滚后重新批量装载文本文件 | 需先清理垃圾数据再回滚重载 |
| 权限要求 | 两端读写权限外加文件系统写权限 | 源读目标写即可 |
表里最值得展开的是查错那一行。异构模式中间步骤生成的文本文件,本质上就是抽取时刻源数据的快照,排障时可以快速回答三个问题:源数据对不对、传输过程有没有丢、装载目标有没有错。同构模式没有这道中间快照,问题发生时很难判断是抽取程序本身的问题,还是源数据在抽取窗口内发生变化导致的。有 5 年以上经验的人看这个维度,关注点应该放在恢复时间目标上,异构成熟之后可以做到按快照快速重建,同构则更依赖目标库自身的恢复能力。
提示:如果源和目标属于不同的分布式环境,且网络走的是广域网,异构模式的异步文件交换通常比同构直连数据库稳定得多。
2.3 网络配置与数据量决定最终选型
环境条件这一段,方案把它归纳成四组。数据总量大时选异构,因为通过网络传输文件的速度比直接通过数据库存取数据要快;数据量小时选同构,避免文件落地的额外开销。广域网或源和目标分属不同数据中心时选异构,局域网或同一数据中心内选同构。源库是否处于分布式环境的判断也很直接:物理架构上分属不同环境就选异构,否则可以同构。最后是源数据复杂度:字段全是文本和数值类型,异构没有问题;一旦源包含图形类字段,把二进制内容导出成文本的代价很大,这时优先考虑同构。
我在几个项目中用过下面的判断顺序:
- 先看网络:跨机房、跨地域,走文件;
- 再看数据量:日均增量超过百万行,优先文件;
- 最后看字段复杂度:含图形等非文本类型,回到直连。
这样三步走下来,基本不会出错。还要补充一个方案里没有明说的点:文本文件可以压缩传输,在网络带宽紧张的场景下,先压缩再传输往往比数据库直连快很多,但这会让异构模式增加一个解压环节,需要在管道设计时预留对应的处理节点。
2.4 混合模式与三指标评估
实际工程中不需要全局二选一。大型事实表走异构,配置维表走同构,这是一个常见的组合。判定标准建议拆成三个可量化指标:单次任务平均耗时、失败重跑平均耗时、源端结构调整后映射修改的人天。三个指标在项目里跟踪两周左右,就能判断当前选型是否需要调整。
3. 从0层DFD到1层DFD:ETL数据流图的分解逻辑
3.1 0层DFD:四个处理节点与回退链路
方案给出的 0 层 DFD 非常标准:业务数据经过 P1 数据抽取变成“未经清洗加工的数据”,再进 P2 数据清洗变成“清洗后的有效数据”,随后 P3 数据转换产出“与目标匹配的数据”,最终 P4 数据加载写入数据仓库。四个节点旁边分别挂了字段映射、数据过滤、业务清洗规则、转换规则、装载策略、批量加载这些控制要素。
0 层图上最值得留意的是 Reject 分支。P2 清洗环节明确留出了 Reject 出口,说明清洗不是单行道,被判定为无效或异常的数据要能被拦截出来,而不是放任其流入下游。很多 ETL 流程只在最终加载环节做校验,一旦清洗层放行了错误数据,到 P4 阶段再发现时已经污染了目标表,回滚成本成倍增加。设计 0 层 DFD 时,建议在每一个处理节点的边界上预留 Reject 通道,用独立目录或状态字段标记异常数据,供后续离线排查。
3.2 P1数据抽取的子过程分解
P1 拆出 P1.1 字段关联、P1.2 增量抽取、P1.3 全量抽取。字段关联负责把源表的业务字段映射到目标或中转模型,增量与全量是两个并行入口,元数据记录抽取方式和抽取频率。
三种抽取的适用场景:全量抽取适合小表、无可靠时间戳的表以及初始化阶段;增量抽取适合大表和具备 update_time 的源表;字段关联则是建立两者之间字段映射关系的桥梁。一个常见误用是把全量抽取用在大表上,导致每天的抽取都成为对源库的巨大压力。正确做法是先全量建立基线,再切到增量,后续每天只拉新增和变更数据。
3.3 P2清洗与P3转换的子过程分解
P2 继续拆成 P2.1 数据替换、P2.2 数据补缺、P2.3 数据规范化。P2.1 处理无效数据,例如把业务系统里杂乱的地区码替换成标准码;P2.2 处理缺失数据,例如为主键为空或关键字段为空的记录补默认值;P2.3 做格式统一,例如日期字段统一成 yyyy-MM-dd。
P3 继续拆成 P3.1 数据/表拆分、P3.2 关联查询、P3.3 数据计算。拆分解决一张源表对应多张目标表,或一张目标表需要合并多行的问题;关联查询解决跨表补充维度信息;数据计算则是按业务口径做金额汇总、比率计算等加工。
这两级分解之间的边界值得强调:P2 处理的是单行内部的问题,P3 处理的是跨行跨表的问题。判断一条记录应该进清洗还是进转换,就看这个规则是否需要参考其他记录。面试时把这个边界讲清楚,比单纯背流程更能体现对 ETL 的理解。
3.4 用DFD定位一次典型故障
以订单金额汇总偏大为例子,按数据流图从右往左排查。先看 P4 加载日志,确认有没有同一个批次被重复执行;再看 P3 的关联查询,检查订单明细表是否和商品表一对多关联导致行数膨胀;如果 P3 没问题,回到 P2 看替换规则,确认是否把两个不同状态的记录替换成了同一个状态码。数据流图的分解在这里相当于一张排障地图,每一层只回答“这一层有没有问题”,把排查范围逐步收窄,最后定位到具体规则。
4. ETL落地:抽取策略、清洗规则与加载回滚
4.1 增量抽取的窗口计算与SQL写法
方案里关于时间窗口的描述是这样的:本次抽取的开始时间为上次抽取的结束时间,本次抽取的结束时间为系统时间或者当前数据库已有记录的最大时间戳。这个设计的目标是定义一个时间区间内源数据的快照,避免重复装载。
实际落地时推荐用存储过程或调度器传参的方式控制窗口:
-- 增量抽取订单表:窗口由两个外部参数控制 -- :last_run 为上次任务成功结束并写入抽取日志的时间点 -- :max_ts 为本次任务启动时从源表取到的最大时间戳,作为窗口右边界 INSERT INTO dwd_orders_incr SELECT order_id, customer_id, amount, status, update_time FROM source.orders WHERE update_time > TO_TIMESTAMP(:last_run, 'YYYY-MM-DD HH24:MI:SS') AND update_time <= TO_TIMESTAMP(:max_ts, 'YYYY-MM-DD HH24:MI:SS');这段 SQL 的逻辑要点是:窗口是左开右闭区间,左边界取上次结束时间,右边界取本次最大时间戳,保证上一批和这一批数据在边界上既不重叠也不遗漏。参数说明::last_run从调度系统的状态表读取,任务成功结束后写入;:max_ts需要先执行一条SELECT MAX(update_time) FROM source.orders取出,再作为绑定变量传入。
不直接使用 sysdate 作为右边界的原因在于,当抽取过程超过预期时长、或源库和目标库的系统时间存在偏差时,窗口会被无意放大,导致下一次运行重复拉取同一批数据。预先取下:max_ts相当于对指定区间做了一次快照,窗口边界不会随执行过程漂移。如果源表数据变更非常频繁,建议把抽取时段安排在生产低谷,并尽量避开目标库的备份窗口。
4.2 字段替换、补缺与规范化的清洗规则
清洗阶段对应 DFD 里的 P2.1、P2.2、P2.3,落地时通常用一段可重复执行的 SQL 完成。下面是一个典型的清洗示例:
-- 清洗客户表:替换无效值、补缺默认值、统一字段格式 UPDATE staging.customer_clean SET customer_name = TRIM(customer_name), phone = LPAD(phone, 11, '0'), province = CASE WHEN province IS NULL OR province = '' THEN 'UNKNOWN' ELSE province END WHERE batch_id = :current_batch;这段 SQL 一次处理三类问题:TRIM去掉姓名两侧空格属于格式统一,LPAD将手机号补足 11 位属于数据替换,province的空值替换成UNKNOWN属于数据补缺。:current_batch是当前批次的编号,用于保证清洗操作只作用于本批次的数据,不会误伤历史数据。
三种清洗操作的选择逻辑可以整理成下面这张规则表:
| 子过程 | 处理对象 | 常见规则示例 |
|---|---|---|
| P2.1 数据替换 | 无效数据 | 状态码映射、去除乱码、统一大小写 |
| P2.2 数据补缺 | 缺失数据 | 默认值、上次有效值、业务兜底值 |
| P2.3 数据规范化 | 格式不统一 | 日期格式统一、手机号补位、去空格 |
清洗规则最容易出问题的地方是“补缺”和“替换”的边界。某些团队把缺失的金额字段用 0 填补,导致下游聚合结果失真;正确处理方式是把这种补缺标记为业务兜底值,在宽表里保留一个标志字段,而不是让下游无法区分真实 0 和补缺 0。
4.3 主键upsert与批量加载策略
方案原文提到:根据目标表的主键确定装载过程中插入或更新记录的策略,主键是新的就插入,主键已存在就用源记录更新目标记录。这个策略在数据库里落地就是 MERGE 语句:
-- 加载:使用 MERGE 实现主键级 upsert,避免重复装载 MERGE INTO dwd_orders t USING staging.orders_incr s ON (t.order_id = s.order_id) WHEN MATCHED THEN UPDATE SET t.amount = s.amount, t.status = s.status, t.update_time = s.update_time WHEN NOT MATCHED THEN INSERT (order_id, customer_id, amount, status, update_time) VALUES (s.order_id, s.customer_id, s.amount, s.status, s.update_time);MERGE 以order_id作为判断依据,能同时处理插入和更新,不需要先 delete 再 insert,避免主键冲突。结合 4.1 的快照窗口设计,即使调大装载时间区段也不会造成数据重复加载,因为重复记录会被 MERGE 吸收。
装载阶段还要考虑批量加载的粒度。一次提交事务过大,失败回滚的时间会很长;一次提交过小,事务开销又会拖慢整体性能。常见做法是以 5000 到 10000 行为一个提交单位,把总处理时间控制在分钟级别。需要强调的是,方案里提到的回滚手段分成两档:装载失败时先回滚上一次装载状态再重新运行程序;必要时把整个数据仓库恢复到某一个时点,再批量装载文本文件。
4.4 Count/Sum对账与质量校验
方案要求用 Sum 和 Count 等聚合函数对源和目标的数据质量进行校验。这段对账逻辑可以做成独立的核查任务:
-- 对账:源端与目标端分别聚合,结果放在同一张结果表中 SELECT 'source' AS side, COUNT(1) AS record_cnt, SUM(amount) AS total_amount FROM source.orders WHERE update_time BETWEEN :start_ts AND :end_ts UNION ALL SELECT 'target', COUNT(1), SUM(amount) FROM dwd_orders WHERE update_time BETWEEN :start_ts AND :end_ts;执行后将两行的record_cnt和total_amount进行对比,数量级不一致时立即能看出窗口重叠或记录缺失。对账维度建议从四个层面逐步收紧:表级行数、关键字段汇总值、去重后主键数、抽样明细对比。这里对应的是方案中提到的数据质量五要素:正确性、一致性、完整性、有效性、可获取性。
5. ETL面试与排错中的三个边界问题
5.1 快照时间戳为什么必须显式定义
面试常问的一个点是:如果源系统时刻都有新数据插入,增量抽取怎么保证不重不漏。答案就是方案里说的“定义某个时间区间内源数据的快照”。这里的关键是右边界必须显式定义,不能简单依赖当前时间。曾有一个项目因为抽取任务执行时间超过 2 小时,直接用 sysdate 作为右边界,导致下一次抽取的左边界晚于上次右边界,中间产生了数据空洞。修复方式是把每次抽取的左边界和右边界都记录进任务状态表,并让右边界在任务启动时固定。
5.2 抽取失败和装载失败的恢复优先级
方案给出的错误处理逻辑可以拆成两条路径:抽取失败时,修正问题后重新从源中抽取,不需要动目标库;装载失败时,回滚到上一次装载状态,再重新运行装载程序。两者的恢复成本不同,优先级也不同。装载失败后处理顺序建议是:先按批次号删除本批已写入的脏数据,再回滚到上一版本,最后重跑本批次。在 Spark 这类批计算框架上编写 ETL 脚本时思路也一样,先清理目标分区再重算该分区,避免整表重跑。
5.3 核查程序的粒度设置
核查程序监控数据传输和装载过程是否有失败或记录缺失。容易忽略的是粒度:只记录“任务失败”对排障没有意义,需要按批次、按表、按主键范围记录“应到行数、实到行数、差异行数、差异主键集合”。同步计算出差异数据落成独立文件,供开发人员直接查看。源端和目标端的系统时钟偏差也会影响时间戳对账,建议统一从一个时间服务同步时钟,并在核查程序里记录每批次的起止时间、左右窗口边界和抽取耗时。核查程序的核心不是记日志,而是让每个批次的时间区间和主键范围都具备可回溯性,排障时才能把一个异常数字准确锁定到某次装载的某一张表。
本文还有配套的精品资源,点击获取