YEAR()和MONTH()函数直接提取日期的年份和月份整数,支持DATE/DATETIME/TIMESTAMP及合法日期字符串,但不自动转换Unix时间戳;用于GROUP BY需组合避免跨年混淆,WHERE中应避免函数导致索引失效而改用范围查询。

YEAR() 和 MONTH() 函数的基本用法
直接对日期字段调用 YEAR() 和 MONTH() 就能拿到整数形式的年份和月份,不需要额外转换或截取字符串。这两个函数接受 DATE、DATETIME、TIMESTAMP 类型,也兼容合法的日期字符串(如 '2024-03-15'),但不接受纯数字或非法格式(比如 '2024/03/15' 在严格模式下会报警告或转为 0)。
常见错误是误传时间戳秒数(如 1710508800),YEAR(1710508800) 会返回 1710,而不是对应年份——MySQL 不会自动把 Unix 时间戳当日期处理,得先用 FROM_UNIXTIME() 转换。
-
YEAR('2024-03-15')→ 返回2024 -
MONTH('2024-03-15')→ 返回3(不是03) -
YEAR(NOW())和MONTH(NOW())可用于当前时间维度聚合
在 GROUP BY 和 WHERE 中拆分并过滤年月
按年份或年+月分组统计时,直接在 GROUP BY 里写 YEAR(date_col) 和 MONTH(date_col) 是可行的,但要注意:单独按 MONTH() 分组会把不同年份的同月数据混在一起(比如把 2023-05 和 2024-05 合并),通常应组合使用。
WHERE 条件中用 YEAR() 和 MONTH() 过滤,性能往往较差——因为无法走索引(函数作用于列会导致索引失效)。更高效的做法是用范围查询替代:
- ❌ 低效:
WHERE YEAR(created_at) = 2024 AND MONTH(created_at) = 3 - ✅ 高效:
WHERE created_at >= '2024-03-01' AND created_at - 如果必须用函数过滤且数据量小,至少给
created_at加普通索引
拼接年月字符串:避免用 CONCAT(YEAR(), MONTH())
想得到 '2024-03' 这种格式,别直接写 CONCAT(YEAR(d), '-', MONTH(d)),因为 MONTH() 返回的是数字 3,不是补零的 03,结果会是 '2024-3'。
正确做法是用 DATE_FORMAT() ——它专为格式化设计,支持 %Y(4位年)、%m(补零月),且能利用日期索引(如果只用于 SELECT 输出,不影响性能):
-
DATE_FORMAT('2024-03-15', '%Y-%m')→'2024-03' -
DATE_FORMAT('2024-3-5', '%Y%m')→'202403'(适合做分区键或归档标识) - 不用
LPAD(MONTH(d), 2, '0'),太绕,可读性差,还多一次函数调用
时区与默认值带来的隐性偏差
YEAR() 和 MONTH() 的结果取决于 MySQL 服务端当前时区(time_zone 系统变量),不是客户端连接时区。如果你的应用跨时区部署,或者表里存的是 UTC 时间但业务要按本地时间统计,直接用函数会出错。
例如:服务器时区是 +00:00,但业务要求按北京时间(+08:00)算月份,2024-03-01 01:00:00 UTC 实际是北京时间 3 月 1 日上午 9 点,但 MONTH(created_at) 仍返回 3;而 2024-03-01 23:00:00 UTC 对应北京时间 3 月 2 日上午 7 点,却被算作 3 月——这可能不符合业务定义的“自然月”。
- 解决方案:统一存储为本地时间,或在查询时用
CONVERT_TZ(created_at, '+00:00', '+08:00')转换后再取年月 - 注意
created_at字段定义为TIMESTAMP时,插入值会自动按当前时区转为 UTC 存储,读取时再转回——这种隐式转换容易让人忽略时区影响











