SQL 函数导致索引失效,因破坏“索引列原始值可直接比对”前提;如 UPPER(name)、YEAR(create_time)等作用于 WHERE 左侧时,数据库无法利用原索引,需改写为 SARGable 形式或用生成列、函数索引等方案。

SQL 函数影响索引,本质是破坏了“索引列的原始值可直接比对”这一前提。数据库只能高效走索引,当它看到 WHERE UPPER(name) = 'ALICE' 或 WHERE YEAR(create_time) = 2023 这类写法时,无法用已建的 name 或 create_time 索引做快速定位——因为索引里存的是原始 name 值和 create_time 时间戳,不是它们的函数结果。
哪些函数操作会让索引失效
只要出现在 WHERE 子句左侧(即被作用于索引列)的函数或表达式,基本都会导致索引跳过:
- 字符串函数:如
UPPER()、LOWER()、SUBSTRING()、TRIM()—— 例如WHERE SUBSTRING(phone, 1, 3) = '138' - 日期函数:如
YEAR()、MONTH()、DATE()、DAYOFYEAR()—— 例如WHERE DATE(order_time) = '2025-12-18' - 数学运算:如
col * 2 > 100、col + 1 = 5—— 左侧不是纯列名 - 隐式类型转换:如字段是
VARCHAR,但查询写成WHERE phone = 13812345678(数字没加引号),MySQL 可能触发全表扫描
改写思路:让条件“SARGable”(可搜索参数化)
核心原则是:把函数从索引列上“移开”,改到常量侧或用等价范围表达。
- 字符串前缀匹配 → 改用 LIKE ‘xxx%’:
❌WHERE LEFT(name, 3) = 'Tom'
✅WHERE name LIKE 'Tom%'(前提是索引在name上,且不带前导 %) - 年份提取 → 改为时间范围:
❌WHERE YEAR(create_time) = 2023
✅WHERE create_time >= '2023-01-01' AND create_time - 大小写不敏感查询 → 改用校对规则或函数索引(MySQL 8.0+):
✅ 创建索引时指定CASE INSENSITIVE校对集,或使用生成列:ALTER TABLE user ADD COLUMN name_lower VARCHAR(50) STORED AS (LOWER(name)); CREATE INDEX idx_name_lower ON user(name_lower); - 数值运算 → 移项还原为纯列比较:
❌WHERE price * 1.1 > 100
✅WHERE price > 100 / 1.1(注意浮点精度是否可接受)
进阶替代方案:绕过函数限制
当业务逻辑确实绕不开函数处理时,可用这些技术保持索引可用:
- 生成列 + 索引(MySQL 5.7+/8.0 推荐):把函数结果持久化为新列并建索引
例:ALTER TABLE orders ADD COLUMN order_year INT AS (YEAR(order_date)) STORED; CREATE INDEX idx_order_year ON orders(order_year); - 函数索引(MySQL 8.0.13+、PostgreSQL 原生支持):
✅CREATE INDEX idx_name_upper ON users ((UPPER(name)));(注意括号语法) - 冗余字段维护:应用层写入时同步保存处理后值,如同时存
email和email_lower,查时直击后者
验证是否生效:必须看执行计划
改写后别凭感觉,用 EXPLAIN 确认是否真正走了索引:
- 关注
type字段:优先看到ref、range、const;避免ALL(全表扫描) - 看
key字段:是否显示你期望的索引名 - 留意
Extra:出现Using filesort或Using temporary是性能隐患信号 - 加
SQL_NO_CACHE排除缓存干扰:EXPLAIN SELECT SQL_NO_CACHE ……