应避免在WHERE中对字段使用YEAR()、MONTH()等函数,因其导致索引失效;正确做法是用DATE_FORMAT(NOW(), '%Y-%m-01')和LAST_DAY(NOW()) + INTERVAL 1 DAY动态生成范围边界,配合>=和<实现高效索引查询。

WHERE 条件里用 YEAR() 和 MONTH() 会出问题
直接写 WHERE YEAR(create_time) = YEAR(NOW()) AND MONTH(create_time) = MONTH(NOW()) 看似合理,但会导致全表扫描——因为对字段加函数会破坏索引。哪怕 create_time 上建了 B+ 树索引,MySQL 也没法用上。
正确做法是算出本月起止时间点,用范围查询:
WHERE create_time >= '2024-06-01' AND create_time < '2024-07-01'
这样能走索引,性能差几个数量级。
用 DATE_FORMAT() 或 LAST_DAY() 动态生成边界值
手动写死日期显然不现实。MySQL 提供了可计算的方案:
-
DATE_FORMAT(NOW(), '%Y-%m-01')得到当月第一天(如'2024-06-01') -
LAST_DAY(NOW()) + INTERVAL 1 DAY得到下月第一天(如'2024-07-01')
组合起来就是安全又动态的写法:
WHERE create_time >= DATE_FORMAT(NOW(), '%Y-%m-01')<br> AND create_time < LAST_DAY(NOW()) + INTERVAL 1 DAY
注意:用 < 而不是 <=,避免跨月数据被误包含;LAST_DAY() 返回的是当月最后一天的日期(如 '2024-06-30'),加一天才是严格边界。
PostgreSQL 或 SQLite 用户别套用 MySQL 写法
不同数据库的日期函数名和行为差异很大:
- PostgreSQL 用
date_trunc('month', now())得本月第一天,date_trunc('month', now()) + interval '1 month'得下月第一天 - SQLite 没有原生月度截断函数,得靠
strftime('%Y-%m-01', 'now'),且要注意它返回的是字符串,比较时隐式转换可能出错 - SQL Server 用
DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)
跨数据库迁移时,光改函数名不够,还得验证边界是否闭合、时区是否一致。
时区和字段类型不匹配是隐形雷
如果应用写入时用的是 UTC 时间,但数据库服务器或会话时区设为 +08:00,NOW() 返回的就是东八区时间,算出来的“本月”就和业务逻辑对不上。
更隐蔽的是字段类型:若 create_time 是 DATETIME 类型但没存秒级精度,或用了 TIMESTAMP 自动转时区,都可能导致边界判断偏移。
最稳妥的方式是统一在应用层生成起止时间戳(带时区信息),传参进 SQL,而不是依赖数据库函数实时计算。

















