WHERE条件中对索引列使用DATE_FORMAT会导致索引失效,因B+树索引存储原始值而非格式化字符串;应改用范围查询或函数索引等替代方案。

WHERE 条件里用 DATE_FORMAT 就别想走索引
只要在 WHERE 子句中对索引列套 DATE_FORMAT,比如 DATE_FORMAT(create_time, '%Y-%m') = '2024-01',MySQL 就会放弃使用 create_time 上的索引,转为全表扫描。这不是配置问题,是 B+ 树索引结构决定的——索引存的是原始 DATETIME 值,不是格式化后的字符串。
常见错误写法:
WHERE DATE_FORMAT(order_date, '%Y-%m-%d') = '2024-07-20'WHERE DATE_FORMAT(submit_time, '%H:%i') BETWEEN '09:00' AND '17:00'
正确替代方式是把函数挪到右边,用范围查询代替:
- 查某天:用
create_time >= '2024-07-20 00:00:00' AND create_time - 查某月:用
create_time >= '2024-07-01' AND create_time - 查某小时:用
create_time >= '2024-07-20 14:00:00' AND create_time
GROUP BY 用 DATE_FORMAT 也会让索引失效
即使你加了 WHERE create_time > '2024-01-01',只要 GROUP BY DATE_FORMAT(create_time, '%Y-%m'),优化器大概率不会用上索引做排序或分组加速。因为分组字段不再是原始列,而是每次计算出的字符串。
更糟的是,DATE_FORMAT(create_time, '%Y年%m月') 这种带中文的写法,连字符集排序都可能出错,下游解析也麻烦。
推荐写法:
- 按天聚合:
GROUP BY DATE(create_time)(前提是WHERE条件也用范围,别写DATE(create_time) = ...) - 按月聚合:
GROUP BY YEAR(create_time), MONTH(create_time)(语义清晰,MySQL 能识别并下推索引扫描) - 要生成整数月标识:
GROUP BY YEAR(create_time) * 100 + MONTH(create_time)(结果如202407,可排序、可索引、方便程序消费)
真需要 DATE_FORMAT 查询?建虚拟列+索引
如果业务确实高频依赖某一种格式化结果(比如固定按 '%Y-%m' 查),又不想改应用逻辑,可以借助 MySQL 8.0.13+ 的函数索引能力。
操作分两步:
- 添加虚拟列:
ALTER TABLE orders ADD COLUMN ym VARCHAR(7) AS (DATE_FORMAT(create_time, '%Y-%m')) STORED; - 在该列建索引:
CREATE INDEX idx_ym ON orders(ym);
之后查询就直接走索引:SELECT * FROM orders WHERE ym = '2024-07';
注意:STORED 表示值物理存储(占空间),VIRTUAL 不存但每次计算(省空间、稍慢)。两者都能建索引,但 STORED 更稳定,尤其配合覆盖索引时。
别在 varchar 时间字段上用 DATE_FORMAT
如果时间字段类型是 VARCHAR(比如存成 '2024-07-20 14:30:00'),再用 DATE_FORMAT 包裹它,不仅索引失效,还可能触发隐式类型转换,甚至在某些数据库(如 YashanDB)里导致查不到数据。
根本解法只有两个:
- 把字段类型改成
DATETIME或TIMESTAMP,再按前述方式优化 - 如果无法改表结构,至少确保所有查询统一用
STR_TO_DATE()转换后再比较,而不是混用字符串和函数
真正卡住性能的,往往不是语法写不对,而是没意识到:索引只认原始值,不认“看起来一样”的计算结果。


















