使用 DATE_SUB 和 LAST_DAY 可安全计算当月起止日期:DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY) 得首日,LAST_DAY(CURDATE()) 得末日,避免字符串拼接导致的格式、NULL 和时区问题,并确保 WHERE 中直接比较字段以利用索引。

用 DATE_SUB 和 LAST_DAY 快速计算当月起止日期
MySQL 里没有直接叫 first_day_of_month() 的函数,但组合 DATE_SUB 和 LAST_DAY 是最稳、兼容性最好的方式。关键在于:别用 NOW() 直接截取年月再拼字符串——容易在跨年或时区错位时出错。
常见错误是写成 STR_TO_DATE(CONCAT(YEAR(NOW()), '-', MONTH(NOW()), '-01'), '%Y-%m-%d'),看似能跑,但遇到 2024-01-01 00:00:00 这类带时间的字段做比较时,会因隐式类型转换漏掉当天数据。
-
SELECT DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY) AS first_day;—— 取今天所在月第一天,返回DATE类型(无时分秒) -
SELECT LAST_DAY(CURDATE()) AS last_day;—— 直接返回当月最后一天的DATE - 如果要包含时间(比如查“从本月1日 00:00:00 到最后一日 23:59:59”),用
DATE_ADD(LAST_DAY(CURDATE()), INTERVAL 23 HOUR + 59 MINUTE + 59 SECOND)
为什么不用 STR_TO_DATE 拼接?
拼字符串看着直观,但实际埋了三个坑:一是 MONTH(NOW()) 返回 1 而不是 01,导致 2024-1-01 这种非法格式;二是 STR_TO_DATE 在格式不匹配时静默返回 NULL,查不到数据还找不到原因;三是 CURDATE() 和 NOW() 在事务中行为不同,前者只读取事务开始时的日期,后者可能随执行时间漂移。
更稳妥的做法是坚持用日期运算函数,它们底层走的是日期逻辑而非字符串解析,不会因为区域设置(如 lc_time_names)改变行为。
在 WHERE 条件里高效使用这两个值
直接把 DATE_SUB 和 LAST_DAY 塞进 WHERE 是可以的,但要注意索引失效风险。比如 WHERE DATE(create_time) >= DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY) 会让 create_time 字段上的索引完全失效。
- 正确写法:
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY) AND create_time - 如果
create_time是DATETIME类型且你只想查整月数据,建议补上时间边界:create_time ,避免最后一天 23:59:59.999 被截断 - 对高频查询,可考虑在应用层预先算好两个日期值传入,减少 SQL 解析开销
注意 LAST_DAY 对月末特殊日期的处理
LAST_DAY('2023-02-29') 这种输入虽然不合理(2023 年不是闰年),但 MySQL 会自动纠正为 2023-02-28;而 LAST_DAY('2024-02-29') 会正常返回 2024-02-29。也就是说,LAST_DAY 内部做了日期合法性校验,不是简单取月份最大天数。
但如果你传入的是字符串(比如 '2023-02-30'),MySQL 先尝试转成日期,失败就返回 NULL,不会报错。所以务必确保输入是合法日期类型,别依赖字符串自动修正。
真正容易被忽略的是:当表里有大量 NULL 的日期字段时,LAST_DAY(NULL) 返回 NULL,参与比较会全部失效,得提前用 IS NOT NULL 过滤。


















