很多人一听到"ETL基础知识"这几个字,脑子里浮现的是画了各种方框箭头的架构图,或者面试必问的八股概念。说实话,我最初接触ETL时也以为它神秘得像炼金术,直到第一次独立负责一张报表的数据接入,才发现ETL的核心就三件事:把数据拿出来,按需处理,再放好。这个过程远比想象中朴实,但坑也比想象中多得多。
这篇内容我尽量用从业者的口吻,把ETL基础拆开讲清楚:它解决什么问题、三个环节各自怎么运作、典型的数据链路长什么样、以及我在实际项目里踩过哪些雷。不管你是刚转行做数据、还是要和数仓团队对接的研发,应该都能从中拿走一些可直接落地的认知。
1. ETL到底是什么:从一次"搬砖"说起
ETL是Extract、Transform、Load三个英文单词的首字母组合,翻译过来就是抽取、转换、加载。它定义了一套标准的数据流动方式,目标是把分散在不同地方、不同格式、甚至不同时效的数据,统一搬运到一个可供分析、查询或再加工的地方。
1.1 一次完整的"搬砖流程"
我经常拿搬家来做类比。假设你要从旧房子(源系统)搬到新房子(目标存储),不能直接把沙发、衣柜、碗碟一股脑丢进货车里就完事。你得先把东西从各个房间收拾出来(Extract),把碎的、脏的、没用的处理掉,给每件物品贴上标签、登记在册(Transform),最后按照新房子的房间规划摆放到正确的位置(Load)。
这个类比听起来简单,但对应到真实的数据世界里,每一个环节都会衍生出大量细节。比如源系统可能是一套老得连文档都没有的业务库,也可能是每天产生几个G日志的文件服务器,还可能是第三方接口返回的嵌套JSON。目标存储可能是关系型数据仓库、列存数据库、MPP数仓,甚至是一堆Parquet文件。不同组合下,ETL的做法完全不同。
1.2 为什么需要单独的"T",直接抽完加载不行吗
这是很多初学者的疑问。我早期的项目里就遇到过天真时刻:从生产库把表原封不动同步到分析库,因为业务方说"数据在外面系统里已经是整理好的,我们只要拷贝一份就行"。结果不出两周就崩了——源库里同一个用户ID在订单表和用户表里类型不一致,一个int,一个是字符串;日期字段有的存字符串"20231001"有的存时间戳;更别提那个"性别"字段里居然有"男""M""1""男生"四种写法。
Transform环节的作用就是把这些历史遗留问题统一收口。它不只是简单的清洗,更重要的是定义"标准口径"。如果没有这个环节,数据进了目标库依然是烂摊子,后续做报表、做模型、做算法,所有工作都会被这些脏数据拖垮。所以,T不是可有可无,而是整个ETL里最体现数据治理水平的部分。
1.3 ETL与ELT:先转后载还是先载后转
现在很多人强调ELT,即抽取后先加载到目标平台,再在平台上做转换。这跟传统ETL的顺序相反。出现这种变化是因为新一代数据平台的计算能力足够强,尤其是云数仓和分布式存储普及之后,把原始数据先一股脑搬进去,再在平台里用SQL做清洗、转换,反而更灵活、成本更低。
我个人的体会是:ETL和ELT没有绝对优劣,只取决于你的目标平台和下游场景。如果转换逻辑复杂且需要大量跨源关联,传统ETL里可能得写一堆脚本搬数据;而ELT让平台统一处理,代码只围绕最终目标做计算。如果你的目标平台本身具备强大的并行计算能力,ELT通常是更省心的选择。但无论哪种顺序,核心思想都是同一个:让数据从杂乱的原始状态,变成可用的、可分析的形态。
2. 三个环节各自的"活法":抽取、转换、加载的边界与细节
既然ETL是三个环节的组合,那每个环节具体需要做什么,才是真正的基础知识。
2.1 抽取:决定"拿什么"和"怎么拿"
抽取是整个流程的起点,它的核心任务是确定数据的来源、频率和方式。说白了就是两件事:数据源头长什么样,以及我多久去取一次。
数据源头的形态大体分几类:关系型数据库、NoSQL数据库、消息队列里的实时流、静态文件(如CSV、Excel、日志文件)、第三方HTTP接口、爬虫抓取的网页数据。针对不同形态,抽取的方式也不同。从数据库抽取时,常见的手段是直连查询或读取日志;从消息队列抽取时,通常需要消费订阅并做好位点记录;从接口抽取时,要特别关注分页和限流策略。
抽取频率则决定了任务的时效性。最常见的做法是定时批量,比如每天凌晨把前一天的全量变化同步过来;时效性要求高的话,可以做成小时级、分钟级,甚至实时监听数据库的变更日志。这里有个容易忽略的点:抽取并不是越频繁越好。每一次抽取都会打到源系统的读取能力上,如果线业务系统本身压力就大,你把抽取频率调到分钟级,极可能给源库带来额外负载,引发生产事故。我见过不止一次因为采集频率过高导致业务数据库CPU飙升的案例,所以评估抽取频率时一定要结合源系统的承受能力。
增量抽取的方式也需要提前想清楚。最朴素的做法是全量抽取——每次把整张表拉一遍,适合数据量小、转换简单的场景。数据量上来之后,增量抽取才是主流:要么基于时间字段(比如update_time),要么基于自增ID,要么干脆解析数据库的binlog日志。选择增量字段时有个很重要的原则:字段必须稳定且绝对单调递增。如果选了一个会被业务修改的时间字段,一旦数据被回刷,增量就会漏数据。
2.2 转换:决定"生成什么样的结果"
转换是整个ETL里工作量最大、逻辑密度最高的环节。它做的事可以归纳为五个层次。
第一层是格式统一。比如时间格式统一成YYYY-MM-DD HH:mm:ss,金额统一为小数两位,编号统一补齐位数。第二层是数据清洗,处理空值、去除重复记录、修正明显的异常值(比如年龄为负、手机号不足11位)。第三层是业务级标准化,把"男""M""1"归一成一个枚举值,把不同系统的客户ID关联到同一个主键上。第四层是口径计算,比如把订单明细按用户聚合出消费金额、把原始事件流加工成会话表。第五层是数据增强,例如通过IP解析城市、通过身份证号推导出生日期,或者和其他维度表关联补充信息。
判断转换做得是否到位,最简单的检验标准是:同一份业务事实,无论从哪个源系统看,在目标表里都应该得到一致的表达。如果两张报表针对"本月订单金额"查出来两个不同数字,先从ETL口径上找原因,大概率是转换逻辑里某个关联字段没对齐或者空值处理不一致。
2.3 加载:决定"数据放到哪、以什么形态放"
加载是把转换好的数据写入目标系统的环节。这里有两层决策:目标存储的选择,以及写入策略的选择。
目标存储选择上,常见的有关系型数据库、列式存储数据库、数据湖文件系统、Elasticsearch这样的检索型引擎。每种存储对写入方式有不同的要求。关系型数据库适合频繁小批量写入,列式存储更适合大批量一次性写入后做分析查询,数据湖场景则通常以文件为单位进行分区写盘。
写入策略主要有全量覆盖、增量追加、更新插入。全量覆盖适合维度表这种数据量小、允许整体重建的表——每次加载前先清空目标表再写入,简单粗暴,但要避免在数据量过大时出现中间状态被读到。增量追加适合日志类、事件类的流水表,每次写入一批新数据,不修改历史。更新插入则适合事实表里既有更新也有新增的记录,需要按业务主键判断:存在就更新,不存在就插入。
这里我得特别提醒一个问题:加载阶段最容易忽视"幂等性"。所谓幂等,就是同一个批次的数