函数索引能走索引,但必须显式创建:MySQL 8.0+ 中需用 CREATE INDEX idx_name ON table ((expression)) 语法声明,且查询条件必须完全匹配该表达式,否则优化器仍放弃索引。

函数操作直接破坏B+树的有序性
MySQL索引底层是B+树,它依赖字段原始值的有序排列快速定位。一旦在WHERE子句中对索引列调用函数(比如DATE(create_time)、LOWER(name)、YEAR(created_at)),MySQL就必须逐行计算函数结果再比对——因为索引里存的是'2025-08-11 14:32:05',不是'2025-08-11'。优化器发现“先算再筛”成本高于全表扫描,就主动放弃索引。
MySQL 8.0+ 的函数索引不是“自动修复”,而是要显式创建
很多人误以为升级到 MySQL 8.0 后,写WHERE DATE(create_time) = '2025-08-11' 就能自动走索引。事实并非如此:函数索引必须手动声明,且只对**完全匹配该表达式**的查询生效。
-
CREATE INDEX idx_date ON orders ((DATE(create_time)));—— 注意双括号,这是 MySQL 8.0+ 函数索引的语法特征 - 只有查询写成
WHERE DATE(create_time) = '2025-08-11'才能命中这个索引 - 如果写成
WHERE create_time >= '2025-08-11' AND create_time ,它走的是普通索引,跟函数索引无关 - 表达式必须确定(deterministic):不能含
NOW()、RAND()等非确定函数,否则建索引会报错ERROR 3750 (HY000)
常见函数失效场景与等价改写对照
多数情况下,改写 SQL 比加函数索引更轻量、更可控。关键原则是:让索引列“裸奔”,把函数挪到常量侧。
-
YEAR(create_time) = 2025→ 改为create_time >= '2025-01-01' AND create_time -
LEFT(name, 3) = 'Tom'→ 改为name LIKE 'Tom%'(前提是没前缀索引,否则可建INDEX idx_name_3 (name(3))) -
price * 1.1 > 100→ 改为price > 100 / 1.1(注意浮点精度,必要时用DECIMAL) -
CONCAT(first_name, ' ', last_name) = 'John Doe'→ 若高频使用,才考虑建函数索引((CONCAT(first_name, ' ', last_name)));否则拆成两个条件first_name = 'John' AND last_name = 'Doe'
函数索引的隐性开销容易被低估
函数索引本质是维护一个隐藏虚拟列 + 对其建索引,这意味着:
- INSERT/UPDATE/DELETE 时,MySQL 要额外计算表达式值并同步更新索引页,写入放大明显
- 表达式越复杂(如嵌套函数、长字符串拼接),CPU 和内存压力越大
- 索引大小可能远超预期:例如
((JSON_EXTRACT(data, '$.user.id')))会把整个 JSON 字段解析一遍再存,不是只存提取结果 - 线上加函数索引需评估锁表现:
ALGORITHM=INSTANT仅支持简单表达式,复杂表达式仍会锁表
真正需要函数索引的,是那些无法改写 SQL、又高频执行的场景——比如日志表按小时分区字段 ((HOUR(created_at))),而不是随手给每个 LOWER(email) 都加一个。


















