LEFT JOIN 能找出增量数据是因为它通过主键/业务键物理比对,配合 WHERE 右表主键 IS NULL 精准捕获目标表缺失而源表存在的记录,结果更可靠,尤其适用于上游无更新标记或时间不准的场景。

LEFT JOIN 为什么能找出增量数据
因为增量数据本质就是「目标表里还没有,但源表里已经存在」的记录,LEFT JOIN 搭配 WHERE ... IS NULL 能精准捕获这种“右表缺失”的行。不是靠时间戳或版本号推断,而是基于主键/业务键做物理比对,结果更可靠——尤其适合上游不提供更新标记、或时间字段不准的场景。
写法必须带 WHERE 右表主键 IS NULL
只写 LEFT JOIN 不过滤,会返回全部左表记录(含已存在的),起不到筛选增量的作用。常见错误是漏掉 WHERE 条件,或误写成 WHERE right_table.id = NULL(SQL 中 = NULL 永远为 false,必须用 IS NULL)。
正确写法示例(同步用户表,以 user_id 为主键):
SELECT s.* FROM source_user s LEFT JOIN target_user t ON s.user_id = t.user_id WHERE t.user_id IS NULL;
-
s.*表示取源表全量字段,实际应按需列出明确列名,避免隐式依赖 - JOIN 条件必须覆盖完整业务主键(如复合主键:
s.order_id = t.order_id AND s.sku_code = t.sku_code) - 若目标表有索引缺失,
t.user_id IS NULL这一条件可能导致全表扫描,务必确保右表关联字段有索引
处理 NULL 值导致的匹配失效
当关联字段本身允许 NULL(比如 email 字段),ON s.email = t.email 会跳过所有 NULL 值行——因为 NULL = NULL 返回 unknown,不成立。这会导致本该识别为“新增”的 NULL 邮箱用户被漏掉。
解决方法是显式补全 NULL 匹配逻辑:
SELECT s.* FROM source_user s LEFT JOIN target_user t ON (s.email = t.email OR (s.email IS NULL AND t.email IS NULL)) WHERE t.email IS NULL;
- 不能只加
OR (s.email IS NULL AND t.email IS NULL)却不保留原等值条件,否则会笛卡尔爆炸 - 如果多个字段可能为 NULL(如
phone,address),每个都要单独展开判断,语句迅速变长,此时建议改用NOT EXISTS更清晰 - 部分数据库(如 PostgreSQL)支持
IS NOT DISTINCT FROM,可简化写法:s.email IS NOT DISTINCT FROM t.email
性能差时优先检查右表索引和统计信息
LEFT JOIN 查差集的性能瓶颈几乎总在右表(target)。即使写了 WHERE t.id IS NULL,优化器仍可能选择对右表全扫描,尤其当右表无合适索引、或表统计信息陈旧时。
- 确认右表关联字段(如
target_user.user_id)有单列索引或作为联合索引最左前缀 - 执行
ANALYZE TABLE target_user(MySQL)或VACUUM ANALYZE target_user(PostgreSQL)更新统计信息 - 避免在 JOIN 条件中对字段做函数操作(如
ON UPPER(s.email) = UPPER(t.email)),这会让索引失效 - 如果右表极大且只增不删,可考虑先用子查询限制范围:
LEFT JOIN (SELECT user_id FROM target_user WHERE updated_at > '2024-01-01') t ...
真正麻烦的是业务键不唯一或存在脏数据——比如源表两个不同用户共用一个手机号,而目标表只存了一个。这时 LEFT JOIN 会因一对多产生重复或漏判,得先清洗再同步。

















