mysql 8.0函数索引易因写法、字符集或函数非确定性而被优化器跳过;推荐用stored生成列+普通索引替代,更稳定可控。

MySQL 8.0 的函数索引本身不“失效”,而是极易因写法、字符集或函数类型不匹配而完全不被使用——你看到的“慢”,大概率是优化器直接跳过了它。
为什么 CREATE INDEX idx ON t((UPPER(name))) 总是不走索引
函数索引对查询语句的字面量要求严苛到近乎偏执:
-
WHERE UPPER(name) = 'JOHN'可以走,但WHERE UPPER(name) = upper('john')就不行(内层函数破坏确定性) - 括号不能多也不能少:
(UPPER(name))是合法索引表达式,UPPER(name)(无外层括号)无法建函数索引 -
JSON_CONTAINS()、CASE WHEN、IF()等非标量/非确定性函数,MySQL 8.0 不允许直接建函数索引 - 如果表字段是
utf8mb4_bin,但函数索引没显式声明排序规则,比较时会触发隐式转换,索引立即失效
改用 STORED 生成列 + 普通索引更稳
尤其适合 JSON_EXTRACT()、DATE_FORMAT()、UPPER() 这类固定路径/逻辑的场景。它把值物理落地,索引行为和普通字段完全一致:
- 建列:
ALTER TABLE users ADD COLUMN name_upper VARCHAR(100) AS (UPPER(name)) STORED; - 建索引:
CREATE INDEX idx_name_upper ON users(name_upper); - 查的时候必须写成:
WHERE name_upper = 'JOHN',不能倒回去写WHERE UPPER(name) = 'JOHN' - 若原字段可能为 NULL,
UPPER(NULL)结果仍是 NULL,WHERE name_upper IS NULL同样能走索引
窗口函数查询中索引不生效的典型陷阱
不是索引“失效”,而是窗口函数执行阶段根本没走到索引扫描环节:
-
OVER (ORDER BY create_time DESC)要高效,必须有(create_time DESC)或(create_time DESC, id)这样的复合索引,单列索引不够 -
PARTITION BY user_id如果user_id没索引,整个窗口计算会退化为全表扫描+临时表 - 在香港等高延迟 VPS 上,
Using filesort和Using temporary会被放大,建议调大sort_buffer_size(如设为 4M)并关闭query_cache_type
最隐蔽也最常被忽略的坑:字符集与排序规则错位
生成列或函数索引的 COLLATE 必须和查询条件的 COLLATE 一致,否则强制转换让索引形同虚设:
- 查当前列排序规则:
SHOW FULL COLUMNS FROM users LIKE 'name_upper';看Collation列 - 建列时显式对齐:
AS (UPPER(name)) STORED COLLATE utf8mb4_0900_ai_ci - 别依赖默认值——源字段是
utf8mb4_bin,生成列却可能是utf8mb4_0900_as_cs,一比较就失效
函数索引不是银弹,它是给极简、极固定、极可控场景准备的;只要查询逻辑稍有变化、字段存在 NULL 或字符集混用,STORED 生成列就是更值得信赖的选择。











