month()和year()函数在where中直接过滤会导致索引失效;应改用范围查询如created_at >= '2023-03-01' and created_at
MONTH() 和 YEAR() 函数在 WHERE 中直接过滤无效?
直接写
WHERE MONTH(created_at) = 3 AND YEAR(created_at) = 2023看似合理,但会导致索引失效——因为对字段施加函数后,数据库无法利用created_at上的 B-tree 索引。实际执行会全表扫描,百万级数据时响应可能从毫秒变秒级。正确做法是把函数挪到右边,让左边保持裸字段:
- ✅ 推荐:
WHERE created_at >= '2023-03-01' AND created_at- ❌ 避免:
WHERE MONTH(created_at) = 3 AND YEAR(created_at) = 2023- 注意:用
而不是 <code>,避免时区或毫秒截断误差MySQL 8.0+ 可用 DATE_FORMAT 做安全的字符串匹配?
如果实在需要按年月字符串筛选(比如配合报表参数),
DATE_FORMAT(created_at, '%Y-%m')比YEAR()+MONTH()组合更简洁,但仍有索引问题。唯一安全用法是配合生成列(generated column)+索引:ALTER TABLE transactions ADD COLUMN ym CHAR(7) STORED AS (DATE_FORMAT(created_at, '%Y-%m'));再建索引:
CREATE INDEX idx_ym ON transactions(ym);,之后就能高效查WHERE ym = '2023-03'。
- ⚠️ 注意:STORED 生成列才支持索引,VIRTUAL 不行
- ⚠️ MySQL 5.7 不支持函数索引,必须用生成列;8.0+ 可直接
CREATE INDEX idx_f ON t((DATE_FORMAT(created_at, '%Y-%m')))PostgreSQL 怎么等价实现?别用 EXTRACT
PostgreSQL 的
EXTRACT(YEAR FROM created_at)和EXTRACT(MONTH FROM created_at)同样导致索引失效。应改用范围查询 +DATE_TRUNC:
- ✅ 推荐:
WHERE created_at >= DATE_TRUNC('month', '2023-03-01'::date) AND created_at- ✅ 更通用:
WHERE created_at >= '2023-03-01'::date AND created_at (无需函数)- ❌ 少用:
WHERE EXTRACT(YEAR FROM created_at) = 2023 AND EXTRACT(MONTH FROM created_at) = 3PostgreSQL 对日期字面量类型推断很稳,
'2023-03-01'自动转为date,不用显式 cast,但加上更明确。SQL Server 的 DATEPART 要小心隐式转换
DATEPART(YEAR, created_at)和DATEPART(MONTH, created_at)行为和 MySQL 类似,也会阻止索引使用。但 SQL Server 有个坑:如果created_at是datetime2类型,而你传入字符串如'2023-03-01',SQL Server 可能因语言设置不同解析成01/03/2023(日/月/年)导致逻辑错误。
- ✅ 安全写法:
WHERE created_at >= '20230301' AND created_at (ISO 格式,无歧义)- ✅ 或用
WHERE created_at >= DATEFROMPARTS(2023, 3, 1) AND created_at- ⚠️ 避免:
WHERE DATEPART(YEAR, created_at) = 2023 AND DATEPART(MONTH, created_at) = 3真正容易被忽略的是时区:如果业务数据跨时区,
created_at存的是 UTC,而你要按本地月份筛选,得先AT TIME ZONE转换,否则凌晨写入的数据可能算错月。












