between and 时间区间连接本质是非等值连接,不可直接写在on中;需确保时间字段为timestamp/datetime类型、统一时区、建立(start_ts, end_ts)联合索引,并根据表大小选择exists或join优化。

用 BETWEEN AND 做时间区间连接,本质是“非等值连接”,不能直接写在 ON 里就完事
很多人一上来就在 JOIN ... ON t1.ts BETWEEN t2.start_ts AND t2.end_ts,语法上没错,但执行效率极低——数据库没法走索引(尤其当 t2 表很大时),容易触发笛卡尔积式扫描。真正可行的前提是:至少一侧的时间字段有有效索引,且连接逻辑能被优化器识别为范围扫描。
先确保时间字段类型和索引匹配
常见翻车点:把 created_at 存成 VARCHAR 或带时区但没统一处理,导致 BETWEEN 比较失效或隐式转换。必须确认:
-
start_ts和end_ts是TIMESTAMP或DATETIME类型,不是字符串 - 两端都使用相同时区(比如全转成
UTC后再比较),避免因CONVERT_TZ导致索引失效 - 对
start_ts和end_ts建联合索引:INDEX idx_time_range (start_ts, end_ts),顺序不能反——因为BETWEEN先查下界
用 EXISTS 替代 JOIN 更可控
当主表(如订单表)远小于区间表(如活动周期表)时,EXISTS 往往比 JOIN 更快,且语义更清晰:
SELECT o.*
FROM orders o
WHERE EXISTS (
SELECT 1 FROM campaigns c
WHERE o.created_at BETWEEN c.start_ts AND c.end_ts
AND c.status = 'active'
);
关键点:
- 子查询里加了
c.status = 'active'这类过滤条件,能大幅缩小扫描范围 - 如果
campaigns表有百万级数据,但只有几十个 active 区间,这个写法会先走status索引再做时间判断 - 反过来,如果
orders是亿级,而campaigns只有几十行,那就该用JOIN,并在orders.created_at上建索引
注意 BETWEEN 的边界行为和 NULL 风险
BETWEEN a AND b 是闭区间,等价于 col >= a AND col 。但实际业务中常遇到:
-
end_ts为NULL表示“长期有效”,此时BETWEEN x AND NULL整个表达式结果为UNKNOWN,记录被过滤掉——得显式补逻辑:(o.created_at >= c.start_ts AND (c.end_ts IS NULL OR o.created_at - 跨天场景下,如果只存日期(
DATE类型),BETWEEN '2024-01-01' AND '2024-01-02'实际覆盖的是00:00:00到00:00:00,第二天的数据会丢——应统一转成带时间的TIMESTAMP再比较 - MySQL 5.7+ 对
BETWEEN在某些函数包裹下(如DATE(created_at))无法使用索引,要避免
时间区间关联从来不是套个 BETWEEN 就能跑得动的事,索引策略、NULL 处理、时区对齐、主被动表角色——漏掉任何一点,线上查一次可能就要几秒起步。










