索引列在 WHERE 中参与运算或被函数包裹时会失效,因索引存储原始值,优化器无法匹配;应将计算移至等号右侧,如用 create_time >= ‘2023-01-01’ 替代 YEAR(create_time) = 2023。

WHERE 条件里对索引列做运算,索引直接失效
MySQL 无法使用索引加速 WHERE name LIKE '%abc' 或 WHERE YEAR(create_time) = 2023 这类表达式——因为索引树里存的是原始值,不是计算后的结果。优化器看到函数或运算包裹列,就放弃走索引,转为全表扫描。
常见踩坑场景:WHERE DATE(create_time) = '2023-01-01'、WHERE id + 1 = 100、WHERE UPPER(name) = 'ABC'。哪怕该列建了索引,也白搭。
- 把计算移到等号右边:用
WHERE create_time >= '2023-01-01' AND create_time 替代 <code>DATE(create_time) - 避免在索引列上用函数:想按年查,优先建范围条件;想忽略大小写,用
COLLATE utf8mb4_0900_as_cs或建函数索引(MySQL 8.0+) - 确认是否真需要计算:比如
id + 1 = 100可直接改写成id = 99
MySQL 8.0+ 函数索引能救急,但有硬限制
如果你必须按计算结果查(比如固定格式的手机号脱敏前缀),可以用函数索引,例如 CREATE INDEX idx_name_upper ON users ((UPPER(name)))。但它不是万能解药。
函数索引只支持确定性函数(如 UPPER、SUBSTR),不支持 NOW()、RAND()、UUID() 等;且查询时 WHERE 中的表达式必须和索引定义 ** 完全一致 **,连空格都不能多一个。
- 索引定义是
(SUBSTR(phone, 1, 3)),那查询必须写WHERE SUBSTR(phone, 1, 3) = '138',写成SUBSTR(phone, 1, 3) = '138'就不命中 - 函数索引不能用于
ORDER BY或GROUP BY的隐式排序,除非语句中也用了完全相同的函数表达式 - 备份 / 恢复或低版本 MySQL(
计算字段单独建索引?先看它是不是“稳定可预测”的
有人会想:我把计算结果存在新字段里,再给它加索引。这可行,但得满足两个前提——值必须由应用或触发器严格维护,且更新逻辑无延迟、无遗漏。
比如订单表加个 status_level 字段,根据 status 值映射为数字,再建索引。但如果状态变更时忘记更新 status_level,或者用异步任务更新导致短暂不一致,查询结果就不可信。
- 仅当计算逻辑极简单、极少变更、且业务能兜住一致性时才考虑冗余字段
- 避免用触发器自动更新:MySQL 触发器性能差,还可能在批量导入或主从同步中出错
- 比冗余字段更稳的做法是:把计算逻辑下沉到应用层预处理,或用物化视图(MySQL 不原生支持,需应用模拟)
EXPLAIN 看不见“隐式类型转换”,但它真会让索引失效
EXPLAIN 输出里 type 是 ALL 或 index,key 是 NULL,基本可以断定索引没走。但有些失效很隐蔽——比如字符串字段存了数字,查询却传整型:WHERE mobile = 13812345678。
MySQL 会把 mobile 列所有值转成数字再比,相当于对列做了隐式函数调用。即使 mobile 是 VARCHAR 并建了索引,也会全扫。
- 统一数据类型:数值就用
INT/BIGINT,字符串就始终用引号,比如WHERE mobile = '13812345678' - 用
SHOW WARNINGS查隐式转换:执行EXPLAIN FORMAT=TRADITIONAL SELECT ……后立刻跟SHOW WARNINGS,看有没有Warning | 1292 | Truncated incorrect DOUBLE value类提示 - 开发阶段用
sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO提前暴露问题
事情说清了就结束。最常被忽略的其实是隐式类型转换——它不报错、EXPLAIN 看着像走了索引(其实没走),查着慢还找不到原因。