正确写法是显式表达“有交集”逻辑并确保字段非空:先用coalesce处理null边界,再用a.start = b.start。

WHERE条件里写BETWEEN会漏掉边界重叠的区间
区间连接最常犯的错,是把范围匹配当成普通等值JOIN来写,用 WHERE a.start = b.start 看似合理,但实际在数据量大、边界密集时容易漏关联——尤其当多个区间首尾相接(比如 [1,5], [5,10])时,BETWEEN 或简单不等式可能因索引失效或NULL处理出错而跳过记录。
真正可靠的写法是显式表达“有交集”逻辑,并确保字段非空:
- 先用
COALESCE处理可能为NULL的边界字段,避免整个条件短路 - 用
a.start (比 <code>BETWEEN更直白,也更容易被优化器识别为范围扫描) - 在
a.start和a.end上建联合索引,顺序必须是(start, end),否则范围扫描效率暴跌
PostgreSQL的OVERLAPS操作符不是万能的
OVERLAPS 看起来很省事:WHERE (a.start, a.end) OVERLAPS (b.start, b.end),但它只在 PostgreSQL 里有,MySQL 和 SQL Server 完全不支持;而且它对半开区间(如 [start, end))的支持不透明——底层仍按闭区间处理,容易在时间戳场景下多连1秒。
更麻烦的是,OVERLAPS 无法利用索引加速,执行计划里常出现 Seq Scan。实测 100 万行表上,手写 a.start 配合索引,性能比 <code>OVERLAPS 快 8 倍以上。
- 跨数据库项目一律不用
OVERLAPS - 若必须用,务必加
AND a.start IS NOT NULL AND a.end IS NOT NULL防止NULL传播 - 时间区间优先用
TIMESTAMP WITH TIME ZONE类型,避免夏令时导致的隐式偏移
LEFT JOIN + 区间匹配会导致笛卡尔爆炸
当左表每行要匹配右表中所有与其重叠的区间时,如果没加限制,很容易从几千行膨胀成几百万行结果。典型表现是查询卡住、内存爆满、返回结果远超预期。
根本原因是:区间交集本身不具备唯一性,一个左表记录可能落在多个右表区间内。必须主动截断或聚合:
- 用
LIMIT 1或ROW_NUMBER() OVER (PARTITION BY a.id ORDER BY b.priority DESC)取最高优先级匹配 - 改用
LATERAL子查询(PostgreSQL / SQL Server 2016+),让右表扫描受左表当前行约束 - 提前用
WHERE b.start 加粗略时间过滤,再进精确交集判断
MySQL 8.0+ 的WINDOW函数救不了区间JOIN性能
有人试图用 ROW_NUMBER() 或 LAG() 来预处理区间合并,再做等值JOIN,但这只适用于“找相邻区间”这类极窄场景。对通用区间关联,窗口函数无法替代索引驱动的范围扫描。
真实瓶颈永远在IO和连接算法上:MySQL 默认用 Block Nested-Loop,区间条件又难走索引,结果就是反复读盘。解决方案很务实:
- 升级到 MySQL 8.0.20+,开启
optimizer_switch='condition_fanout_filter=on',让优化器更准估算交集基数 - 把右表区间按
start分片(如每万条一个子表),用UNION ALL分段JOIN,手动控制膨胀规模 - 如果业务允许,把高频查询的区间关系物化到一张关联表里,用触发器或应用层维护,查的时候直接等值JOIN
区间JOIN从来不是单靠SQL技巧能搞定的事。索引设计、数据分布、NULL语义、目标数据库的优化器脾气——少踩一个,查询就快一倍。别信“一条SQL解决”的说法,先看执行计划里的 type 是不是 range,再说话。










