JOIN时需根据业务语义选择连接方式:找“仍存在且有变化的记录”用INNER JOIN,获取“所有历史+当前快照”则用UNION ALL或FULL OUTER JOIN(MySQL 8.0+支持,旧版需LEFT+RIGHT JOIN模拟);查新增记录用LEFT JOIN配合WHERE history.id IS NULL,并注意逻辑删除和NULL处理;字段合并优先用COALESCE配合NULLIF避免空字符串干扰;务必通过索引和时间范围剪枝提升性能,避免分布式JOIN陷阱。

JOIN时历史表和当前表的主键对齐问题
历史表和当前表字段相同但主键可能不一致,直接ON history.id = current.id会漏掉新增或已删除的记录。必须先明确业务语义:是要找“仍存在且有变化的记录”,还是“所有历史+当前快照”?前者用INNER JOIN,后者常用UNION ALL或FULL OUTER JOIN(部分数据库不支持)。MySQL 8.0+ 支持FULL OUTER JOIN,但老版本得用LEFT JOIN + RIGHT JOIN + UNION模拟。
用LEFT JOIN查“当前有但历史没有”的新增记录
典型场景:同步增量数据时识别新插入的行。关键不是只写LEFT JOIN,而是要配合WHERE history.id IS NULL过滤。
SELECT current.* FROM current LEFT JOIN history ON current.id = history.id WHERE history.id IS NULL- 注意
history.id不能是允许NULL的字段,否则IS NULL判断失效 - 如果历史表有逻辑删除标记(如
is_deleted = 1),需额外排除:AND (history.is_deleted IS NULL OR history.is_deleted = 0)
用COALESCE处理历史字段缺失导致的NULL
合并后某些字段在历史表里为空(比如新字段刚加进当前表),直接SELECT history.field, current.field会看到大量NULL。用COALESCE(history.field, current.field)能优先取历史值、缺则 fallback 到当前值。
- 但要注意类型兼容性:
COALESCE(history.updated_at, current.created_at)要求两个字段都是TIMESTAMP或可隐式转换 - 字符串字段慎用
COALESCE(history.name, current.name)——如果history.name是空字符串而非NULL,不会触发fallback - 更稳妥写法:
COALESCE(NULLIF(history.name, ''), current.name)
性能陷阱:JOIN前没加时间范围或索引
历史表动辄上亿行,没约束条件就JOIN,查询可能跑十几分钟甚至OOM。必须提前剪枝。
- 给历史表加复合索引:
CREATE INDEX idx_history_id_updated ON history(id, updated_at) - JOIN前用子查询或CTE限制历史数据范围:
WITH recent_history AS (SELECT * FROM history WHERE updated_at > '2024-01-01') - 避免在
ON条件里用函数,比如ON DATE(history.updated_at) = DATE(current.created_at)会导致索引失效
真正麻烦的是跨分区或跨库的历史表——这时候JOIN变成分布式join,延迟和一致性都得另想办法,别只盯着SQL写法。

















