应使用左闭右开区间,结束值取下一日零点;避免DATE()函数导致索引失效;推荐生成列索引或应用层时间归一化。

WHERE date_col >= '2024-01-01' AND date_col
直接用 BETWEEN '2024-01-01' AND '2024-01-31' 看似合理,但几乎所有数据库都会把 '2024-01-31' 隐式转成 '2024-01-31 00:00:00',导致当天 00:00:01 之后的所有记录全被漏掉。左闭右开区间(>= + )天然覆盖整日,不依赖字段是否带时间、也不受毫秒精度影响。
常见错误现象:
- 查“1月数据”,结果里没有
'2024-01-31 15:22:03'这类记录 - 执行计划显示
type: ALL(全表扫描),因为用了DATE(date_col) = '2024-01-31'导致索引失效
实操建议:
- 起始值用
'2024-01-01'(自动补00:00:00没关系,>= 包含它) - 结束值必须是**下一日零点**,如查 1 月就用
'2024-02-01',不是'2024-01-31 23:59:59'——后者在 MySQL 5.6 或 PostgreSQL 中仍可能漏掉毫秒级数据 - 字段为
TIMESTAMP WITH TIME ZONE时,确保比较值也带时区,例如 PostgreSQL 中写created_at >= '2024-01-01'::timestamptz AT TIME ZONE 'Asia/Shanghai'
字段类型是 DATETIME/TIMESTAMP 时,别用 DATE() 函数包裹
DATE(create_time) = '2024-01-31' 会导致索引完全失效,优化器无法做 range scan,只能走 index 或全表扫描。哪怕字段上有 create_time 的 B-tree 索引,这个写法也白搭。
使用场景:
- 高频查询、数据量 >10 万行时,性能下降明显(实测慢 3–10 倍)
- 分库分表环境下,路由键若依赖该字段,函数调用还可能导致跨分片查询
替代方案:
- 建生成列 + 索引:MySQL 5.7+ 可加
ADD COLUMN create_date DATE AS (DATE(create_time)) STORED,再对create_date建索引 - 应用层传参前就按日归一化时间:传入
start = '2024-01-31T00:00:00'和end = '2024-02-01T00:00:00',SQL 直接用>=和
动态查“最近 N 天不含今天”要小心 CURDATE() 边界
想取“截至昨日的最近 3 天”,即 T−1、T−2、T−3 三个自然日,不能写 date_col > DATE_SUB(CURDATE(), INTERVAL 3 DAY)——这会包含今天。
正确逻辑是左闭右开区间 [CURDATE() - INTERVAL 3 DAY, CURDATE()):
- MySQL:
WHERE date_col >= DATE_SUB(CURDATE(), INTERVAL 3 DAY) AND date_col - PostgreSQL:
WHERE date_col >= CURRENT_DATE - INTERVAL '3 days' AND date_col - SQL Server:
WHERE date_col >= DATEADD(day, -3, CAST(GETDATE() AS DATE)) AND date_col
容易踩的坑:
-
CURDATE()返回服务器本地日期,若数据库时区设为 UTC,而业务按北京时间统计,结果会整体偏移一天 - 字段是
DATETIME但没显式 cast,CURDATE()在比较时可能被隐式转成'2024-01-31 00:00:00',而你的数据是'2024-01-31 14:22:05',条件仍成立;但一旦字段是TIMESTAMP,时区转换会让行为不可控
BETWEEN 不是语法错误,但语义易误导且难调试
很多人坚持用 BETWEEN 是因为它“看起来像自然语言”,但实际调试时发现数据少一天,第一反应往往是检查数据本身,而不是重读边界定义。BETWEEN 的闭区间特性在日期场景下几乎总是反直觉的。
性能与兼容性影响:
- MySQL/PostgreSQL/SQL Server 对
BETWEEN '2024-01-01' AND '2024-01-31'的解释一致(都补00:00:00),但 Oracle 的DATE类型本身就含时间部分,行为略有不同 - 当字段是字符串类型(如
VARCHAR(10)存'2024-01-01'),BETWEEN按字典序比较,'2024-01-31'会比'2024-01-2'小,结果完全错乱
真正关键的不是“能不能用”,而是“改起来成本高不高”。线上已有大量 BETWEEN 查询,临时改成开区间需同步修改所有调用方、测试用例和监控告警规则——漏一处就出问题。

















