应构造左闭右开时间范围以利用索引:WHERE trans_time >= DATE_FORMAT(NOW(), '%Y-%m-01') AND trans_time < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)。

WHERE子句里怎么写“本月”条件才可靠
直接用 MONTH(date_col) = MONTH(NOW()) AND YEAR(date_col) = YEAR(NOW()) 看似直观,但会强制全表扫描——因为对列应用函数(MONTH()、YEAR())导致索引失效。真正靠谱的做法是构造一个左闭右开的时间范围,让数据库能走 date_col 上的索引。
推荐写法:用日期边界代替函数提取
假设流水时间字段叫 trans_time,类型为 DATETIME 或 TIMESTAMP,推荐这样写:
WHERE trans_time >= DATE_FORMAT(NOW(), '%Y-%m-01') AND trans_time < DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)
这个写法的关键点:
-
DATE_FORMAT(NOW(), '%Y-%m-01')固定生成当月第一天的零点时间(如'2024-06-01 00:00:00'),不依赖具体时区设置 -
DATE_ADD(..., INTERVAL 1 MONTH)是安全的进月操作,比手动拼字符串或加30天更准确(避开2月/闰年问题) - 使用
而非 <code> 配合“下月1号”,可完整覆盖最后一秒(比如 <code>'2024-06-30 23:59:59.999')
注意时区和字段类型陷阱
如果数据库服务器时区与业务时区不一致(比如服务器在UTC,而财务要求按东八区算“本月”),NOW() 就不可靠。此时必须显式转换:
- MySQL 8.0+ 可用
CONVERT_TZ(NOW(), '+00:00', '+08:00')先转时区再取月初 - 若
trans_time是DATE类型(无时分秒),可简化为trans_time >= '2024-06-01' AND trans_time ,但注意硬编码会失去动态性 - PostgreSQL 用户请换用
date_trunc('month', now())和date_trunc('month', now()) + interval '1 month'
别忽略索引是否真的生效
写了正确条件不等于就快了。务必确认:
-
EXPLAIN输出中type是range或更好,且key显示用了trans_time对应的索引 - 如果表有联合索引(如
(status, trans_time)),而查询还带了WHERE status = 'success' AND trans_time >= ...,那这个联合索引可能比单列索引更优 - 某些旧版本 MySQL 对
DATE_ADD表达式推导能力弱,可先用变量存边界值再引用,避免重复计算
边界计算逻辑本身很简单,但一旦混入时区、字段精度、索引策略,就容易漏掉一两个环节导致查询变慢或结果出错。

















