第六章 数据加载策略:全量加载、增量加载与SCD处理

数据加载,说白了就是把处理好的数据写进目标系统。这一步看似简单,但坑特别多。我见过不少团队,ETL跑得飞快,结果加载环节频频翻车——不是性能崩了,就是数据对不上。

这一章,咱们就聊聊加载策略的那些事。我会结合自己踩过的坑,把全量加载、增量加载、SCD处理以及性能优化这些核心内容讲透。

6.1 全量加载:简单粗暴,但别滥用

全量加载,就是每次把源表的所有数据全部覆盖到目标表。实现起来最简单:

-- 全量加载示例(PostgreSQL)
TRUNCATE TABLE target_schema.sales;
INSERT INTO target_schema.sales
SELECT * FROM source_schema.sales;

嗯,这里要注意——全量加载虽然代码简单,但代价很高。我曾在某电商项目里,每天全量加载一张5亿行的订单表,结果ETL窗口从2小时膨胀到8小时,最后不得不重构。

避坑指南: 我曾经接手过一个项目,全量加载时直接DELETE再INSERT,结果事务日志撑爆了磁盘。后来改成TRUNCATE + INSERT,性能提升了3倍。记住:全量加载优先用TRUNCATE,别用DELETE。

什么时候用全量加载?

  • 数据量小(百万级以下)
  • 目标表需要完全重建(比如维度表重构)
  • 源系统不支持增量识别

6.2 增量加载:高效但需要技巧

增量加载只处理新增或变化的数据。核心思路就两个:

  1. 基于时间戳:源表有最后修改时间字段
  2. 基于CDC:通过日志捕获变更

我个人习惯用时间戳方式,简单可靠。但要注意——源表的时间戳字段必须有索引,否则你会哭的。

-- 增量加载示例(基于时间戳)
INSERT INTO target_schema.sales
SELECT * FROM source_schema.sales
WHERE last_modified > (
    SELECT COALESCE(MAX(last_loaded), '1970-01-01')
    FROM target_schema.load_control
    WHERE table_name = 'sales'
);
我的经验: 时间戳增量有个坑——如果源表数据被物理删除,你永远不知道。所以建议配合软删除或CDC一起用。

6.3 Merge/Upsert:增量更新的核心

增量加载不只是INSERT,还要处理更新。这时候就需要Merge(也叫Upsert)。

说白了,Merge就是:存在就更新,不存在就插入。不同数据库语法略有差异:

-- PostgreSQL的Merge语法
MERGE INTO target_schema.sales AS t
USING source_schema.sales AS s
ON t.order_id = s.order_id
WHEN MATCHED THEN
    UPDATE SET 
        amount = s.amount,
        last_modified = CURRENT_TIMESTAMP
WHEN NOT MATCHED THEN
    INSERT (order_id, amount, last_modified)
    VALUES (s.order_id, s.amount, CURRENT_TIMESTAMP);

你想想看,如果没有Merge,你得先查一遍判断是否存在,再决定INSERT还是UPDATE。不仅代码啰嗦,性能也差。

避坑指南: 我曾经在MySQL上做Merge,发现它不支持标准Merge语法,只能用INSERT ... ON DUPLICATE KEY UPDATE。不同数据库的Upsert实现差异很大,迁移时一定要测试。

6.4 SCD(缓慢变化维)处理

SCD是维度建模里的经典问题。说白了就是:维度表的属性会变,你怎么处理历史数据?

常见的三种类型:

类型 处理方式 适用场景
SCD Type 1 直接覆盖旧值 不关心历史变化(如用户手机号)
SCD Type 2 保留多条记录,加生效时间 需要追溯历史(如产品分类)
SCD Type 3 增加字段存旧值 只关心上一次变化

我在项目中用得最多的是Type 2。实现起来也不复杂:

-- SCD Type 2 实现示例
-- 先关闭旧记录
UPDATE dim_customer
SET is_active = FALSE,
    end_date = CURRENT_DATE - 1
WHERE customer_id = 123
  AND is_active = TRUE;

-- 再插入新记录
INSERT INTO dim_customer (
    customer_id, name, city, 
    start_date, end_date, is_active
)
VALUES (
    123, '张三', '北京',
    CURRENT_DATE, '9999-12-31', TRUE
);
我的经验: Type 2虽然灵活,但维度表会越来越大。我建议定期归档历史数据,或者用拉链表来管理。另外,别忘了给customer_id + is_active加联合索引,否则查询会慢到怀疑人生。

6.5 加载性能优化

加载性能优化,我总结了几个核心原则:

  • 批量提交:别一条一条INSERT,用批量操作。我习惯每1000条提交一次。
  • 关闭约束和索引:加载前禁用索引和外键,加载完再重建。能快5-10倍。
  • 并行加载:按分区或分片并行写入。但要注意目标库的连接数限制。
  • 使用原生加载工具:比如PostgreSQL的COPY、MySQL的LOAD DATA INFILE,比INSERT快一个数量级。
-- PostgreSQL COPY命令示例(比INSERT快10倍)
COPY target_schema.sales
FROM '/data/sales_20240101.csv'
DELIMITER ','
CSV HEADER;
避坑指南: 我曾经在加载时同时开了20个并行线程,结果目标库连接池被打满,其他任务全挂了。后来改成按分区串行+线程池控制,才稳定下来。并行虽好,别贪心。

6.6 知识体系总览

下面这张图,是我对本章知识体系的总结。你可以把它当作一个快速索引:

数据加载策略知识体系 数据加载策略 全量加载 TRUNCATE + INSERT 增量加载 时间戳 / CDC Merge/Upsert 存在更新,不存在插入 SCD缓慢变化维 Type 1:覆盖 Type 2:多版本 Type 3:旧值字段 加载性能优化 批量提交 关闭索引/约束 并行加载 选择策略:数据量 + 变更频率 + 历史需求

这张图把本章的核心内容串起来了。你想想看,实际项目中往往不是只用一种策略,而是组合使用。比如:维度表用SCD Type 2,事实表用增量Merge,小表用全量加载。

核心总结:

  • 全量加载:简单但成本高,适合小表或重建场景
  • 增量加载:高效但需要源系统支持
  • Merge/Upsert:增量更新的标准做法
  • SCD:维度变化的历史管理方案
  • 性能优化:批量、关闭约束、并行、原生工具

好了,这一章的内容就到这里。加载策略选对了,ETL就成功了一半。剩下的,就是多测试、多踩坑、多总结。


公众号:蓝海数据掘金营,微信deep3321