MySQL函数索引必须用双括号((expression))定义,WHERE条件须字面完全匹配索引表达式,否则不生效;DML实时计算索引项,需注意写放大、锁竞争与类型风险;务必用EXPLAIN验证。

CREATE INDEX 语句必须带双括号,否则直接报错;WHERE 条件必须字面完全匹配索引表达式,否则优化器根本不会用它。
建索引时必须写成 ((expression)) 形式
MySQL 严格区分普通列索引和函数索引,语法上强制要求表达式外层加一对括号。漏掉或错位都会失败:
-
CREATE INDEX idx_lower_email ON users ((LOWER(email)));✅ 正确 -
CREATE INDEX idx_bad ON users (LOWER(email));❌ 缺外层括号,ERROR 1064 -
CREATE INDEX idx_bad2 ON users ((email));❌ 单列名套括号也不行,不是“真表达式” -
CREATE INDEX idx_bad3 ON users (SUBSTRING(name, 1, 10));❌ 同样缺括号,且未声明长度会触发 Function is not allowed in index 错误
常见可用表达式包括:DATE(created_at)、UPPER(name)、SUBSTRING(message, 1, 5)、JSON_EXTRACT(json_col, '$.user.id'),但不能含子查询、用户变量或非确定性函数(如 NOW()、RAND())。
查询条件必须跟索引定义“一字不差”
优化器不做语义推导,只做字面匹配。哪怕逻辑等价,写法不同就走不了索引:
- 建了
CREATE INDEX idx_upper_name ON users ((UPPER(name))); - 只有
WHERE UPPER(name) = 'JOHN'或WHERE UPPER(name) LIKE 'JO%'能命中 -
WHERE name = 'john'不行(没包装函数) -
WHERE UPPER(name) LIKE '%JO'不行(后缀匹配无法利用 B+ 树) -
WHERE name LIKE 'joh%'不行(大小写不一致,且未调用 UPPER)
对 JSON 字段也一样:CREATE INDEX idx_email ON logs ((CAST(properties ->> '$.request.email' AS CHAR(100))));,查询必须写成 WHERE CAST(properties ->> '$.request.email' AS CHAR(100)) = 'a@b.com',少一个 CAST 或类型长度不一致都会失效。
DML 会实时计算并写入索引项,有真实开销
函数索引不是“懒加载”,每次 INSERT / UPDATE 都要执行表达式计算,并把结果存进 B+ 树页:
- 写放大:每行变更多一次函数计算 + 多一条索引记录写入
- 锁竞争:表达式计算本身不加锁,但索引页修改仍参与 latch 竞争
- 空间占用:结果值占存储空间,单键最大 3072 字节(InnoDB 限制),
MD5(content)比LEFT(content, 200)更重 - 类型隐式转换风险:
(price * 1.1)若price是DECIMAL(10,2),乘法后精度变化可能导致索引项长度超限或比较行为异常
用 EXPLAIN 验证是否真正生效
别信直觉,一定要看 EXPLAIN 输出:
-
key: idx_upper_name→ 明确用了该函数索引 -
Extra: Using index→ 覆盖索引,无需回表 -
Extra: Using where; Using index→ 表达式已用于过滤,且覆盖 -
key: NULL或出现Using filesort/Using temporary→ 函数索引没被选中,得检查表达式是否完整写出、是否有不可索引操作(比如嵌套子查询或非确定性函数)
最常被忽略的是:函数索引底层基于虚拟列实现,但它不存原始值,只存计算结果——这意味着你无法用 SELECT 直接查出这个“隐藏列”,也不能在 ORDER BY 中引用它,除非 WHERE 条件里也用了完全相同的表达式。


















