函数索引仅在查询条件与建索引时的表达式字面完全一致时才生效;mysql 8.0.13+支持,必须用双括号语法如((upper(email))),且仅允许确定性函数,不支持主键或外键引用。

函数索引不是“给字段套个函数就能加速”,它只在查询条件与建索引时的表达式字面完全一致时才生效;MySQL 8.0.13+ 才支持,且必须用双括号语法,否则建不成功或压根不走索引。
CREATE INDEX 必须带外层双括号
MySQL 把 (UPPER(name)) 和 UPPER(name) 当作两类东西:UPPER(name) 被解析为列名或语法错误,((UPPER(name))) 才被识别为函数索引表达式。
- ✅ 正确:
CREATE INDEX idx_upper_name ON users ((UPPER(email))); - ❌ 错误:
CREATE INDEX idx_upper_name ON users (UPPER(email));(少一层括号,报 ERROR 1064) - ❌ 错误:
CREATE INDEX idx_upper_name ON users ((email));(单列加括号 ≠ 函数索引,报 “Functional index expression cannot be a column reference”) - ❌ 错误:
CREATE INDEX idx_upper_name ON users (('UPPER(email)'));(引号包裹整个表达式,语法非法)
WHERE 条件必须和索引表达式字面完全匹配
函数索引不提供“语义等价”推导能力。建了 ((UPPER(name))),只有 WHERE UPPER(name) = 'JOHN' 能走索引;WHERE name = 'john'、WHERE LOWER(name) = 'john'、WHERE name LIKE 'joh%' 全部失效。
- 日期场景:建了
((DATE(created_at))),则WHERE DATE(created_at) = '2026-06-01'可命中;但WHERE created_at >= '2026-06-01'或WHERE YEAR(created_at) = 2026不行 - 邮箱域名提取:建了
((SUBSTRING_INDEX(email, '@', -1))),则WHERE SUBSTRING_INDEX(email, '@', -1) = 'gmail.com'可走索引;WHERE email LIKE '%@gmail.com'不行 - JSON 字段:建了
((JSON_EXTRACT(profile, '$.status'))),需确保返回类型明确,建议补CAST(... AS CHAR)避免隐式转换失败
哪些函数能用、哪些不能用
函数索引复用生成列机制,只允许确定性、无副作用、可比较的函数。可用与否不看文档宣传,而看 MySQL 是否允许它出现在 AS 子句中。
- ✅ 常见可用:
UPPER()、LOWER()、TRIM()、DATE()、YEAR()、SUBSTRING()、ABS()、COALESCE()、CAST()、JSON_EXTRACT() - ❌ 绝对禁用:
NOW()、CURDATE()、RAND()、UUID()、USER()、DATE_FORMAT()(非确定)、子查询、自定义函数、存储过程内函数 - ⚠️ 算术表达式允许但有代价:
((price * (1 + tax_rate)))可建,但每次 INSERT/UPDATE 都要实时计算,数据量大时写入延迟明显上升
验证是否真被用了,别只看 SHOW INDEX
SHOW INDEX FROM t1 只能确认索引存在,Expression 列非 NULL 表示是函数索引,但无法判断是否被查询命中。
- 必须用
EXPLAIN SELECT ...看key字段是否显示你的函数索引名 - 注意
type字段:从ALL(全表扫描)变成range或ref才算真正生效 - 线上建索引前务必在从库或低峰期测试:已有百万级数据时,
CREATE INDEX会触发全表扫描计算虚拟列值,可能卡住 DML
最常被忽略的一点:函数索引不可作为主键或外键目标,也不能被 SELECT * 返回——它只是索引结构里的一个隐藏虚拟列,不参与结果集构建。想查表达式结果,还得在 SELECT 中显式写函数调用。











