应避免在where中对日期列使用year()、date()等函数,因其导致索引失效而全表扫描;正确做法是将函数移至常量侧,改用范围查询,如year(create_time)=2023改为create_time>='2023-01-01' and create_time

直接改写查询,别在 WHERE 里对日期列用 YEAR()、DATE()、DATE_FORMAT() 这类函数——这是最有效、兼容性最好、且不需要升级 MySQL 版本的解法。
为什么 DATE(create_time) = '2023-10-01' 会全表扫描
索引(B+树)存的是 create_time 的原始值,比如 '2023-10-01 14:22:33'。一旦你写 DATE(create_time),MySQL 就得对每一行都执行一次函数计算,再比对结果,索引完全没法跳过无关数据。
- 哪怕
create_time上有联合索引,只要函数作用于它,整条索引路径就失效 -
EXPLAIN里看到type: ALL或key: NULL就是典型信号 - 函数索引(MySQL 8.0+)能缓解,但不是万能:它只加速该函数表达式本身,不能替代原列索引的通用性
把函数挪到常量侧:范围查询替代函数调用
核心原则是让索引列“裸奔”出现在 WHERE 左侧,右侧用计算好的时间边界。
-
YEAR(create_time) = 2023→ 改成create_time >= '2023-01-01' AND create_time -
DATE(create_time) = '2023-10-01'→ 改成create_time >= '2023-10-01' AND create_time -
DATE_FORMAT(create_time, '%Y-%m') = '2023-10'→ 改成create_time >= '2023-10-01' AND create_time
注意用 而非 <code>,避免跨月/跨年时多查一行;时间类型要和字段精度匹配(比如 <code>DATETIME 别漏掉 00:00:00)。
MySQL 8.0+ 函数索引不是银弹,慎用
虽然可以建 CREATE INDEX idx_year ON orders ((YEAR(create_time)));,但它只对 WHERE YEAR(create_time) = 2023 有效,对 WHERE create_time > '2023-06-01' 没用,也不能用于排序或覆盖查询。
- 函数索引字段必须用双括号包裹,语法容易写错:
((YEAR(create_time)))不是(YEAR(create_time)) - 无法在函数索引上做范围扫描,只能等值匹配
- 如果业务中既有
YEAR()查询,又有按时间范围查、排序、分组的需求,优先保原始列索引,函数索引仅作补充
隐式转换 + 函数 = 双重失效,务必检查参数类型
比如 create_time 是 DATETIME,但你在应用层传了字符串 '2023-10-01' 且没加单引号(或 ORM 自动转成数字),MySQL 可能触发隐式转换,叠加函数操作后优化器直接放弃索引。
- 用
SHOW WARNINGS查看是否出现Warning Code 1292: Truncated incorrect DOUBLE value - 确认参数传入时是带引号的字符串,或使用预处理语句绑定正确类型
- 在
EXPLAIN FORMAT=JSON输出里重点看key_column和used_key_parts是否为空
真正难的不是写出函数索引,而是判断哪些查询路径必须走范围扫描、哪些可以接受函数索引兜底——业务语义和查询模式决定索引策略,不是语法能自动推导的。











