首页/数据/数据库速查/函数索引(Functional Index,8.0.13+)
性能与调优57 条

函数索引(Functional Index,8.0.13+)

按表达式建索引:对列做函数/表达式查询可走索引(如 MONTH(col)、JSON 字段提取),语法是加括号的表达式。

数据库速查数据更新于 2026-10-13

函数索引是什么

函数索引(8.0.13+ 支持)按表达式而不是按列建索引:MONTH(created_at)、CAST(data->>'$.type' AS CHAR(16)) 这类表达式可以成为索引键。实现上 MySQL 会隐式创建一个隐藏虚拟列再在其上建索引。它解决的是「列被函数包住导致索引失效」的老问题,但代价是可读性下降,并且查询里的表达式必须与索引表达式完全一致才能命中。

基本信息

版本要求MySQL 8.0.13 起支持表达式索引,同版本起支持 JSON 路径表达式索引
语法要点表达式必须用额外一对括号包起来:(表达式)
实现方式隐藏虚拟列 + 普通索引
匹配条件查询中的表达式需与索引表达式完全一致才可使用
查看SHOW INDEX 与 information_schema 中会出现隐藏列名

典型使用场景

  • 按时间片段查询:经常按 MONTH(created_at) 或 DATE(created_at) 过滤时避免全表扫描
  • JSON 字段检索:对 data->>'$.type' 这类提取表达式建索引,支撑按 JSON 属性筛选
  • 大小写不敏感检索:对 LOWER(email) 建索引,配合 LOWER() 查询稳定命中
  • 表达式计算过滤:按 UPPER(code) 或截取后拼接的键值查询
  • 老查询无法改写时:查询由框架生成不便改写,用函数索引兜住性能

创建与验证

创建与验证
-- 对月份表达式建索引,注意外层要多一对括号
ALTER TABLE appdb.orders ADD INDEX idx_month ((MONTH(created_at)));
-- 查询表达式必须与索引表达式完全一致才可能命中
EXPLAIN SELECT id FROM appdb.orders WHERE MONTH(created_at) = 10;
-- JSON 路径表达式索引(8.0.13+)
ALTER TABLE appdb.events ADD INDEX idx_js ((CAST(data->>'$.type' AS CHAR(16))));
EXPLAIN SELECT id FROM appdb.events WHERE CAST(data->>'$.type' AS CHAR(16)) = 'click';
-- 确认索引已注册,含隐藏列名
SHOW INDEX FROM appdb.orders WHERE Key_name = 'idx_month';

与普通索引对比

维度普通索引函数索引
索引键列值本身表达式计算结果
命中条件列直接比较即可表达式必须完全一致
可读性定义直白需对照定义才能理解
维护成本写入维护列值写入时需计算表达式
优先顺序首选改写查询不可行时再用
能用改写解决时优先改写:把 MONTH(created_at) = 10 改成 created_at >= '2026-10-01' AND created_at < '2026-11-01',既走普通索引又语义清晰。函数索引适合确实无法改写的生成式查询。
表达式里的类型转换差异(例如 CHAR(16) 与 CHAR(20))会让优化器认为是不同表达式,索引直接失效,定义与查询要保持逐字一致。

易错点

  • 建了 MONTH(created_at) 的函数索引却用范围条件查询:表达式不匹配,索引完全用不上,需要按实际查询写法选索引形式
  • 表达式写法有细微差异(多空格、类型长度不同、大小写不同):优化器识别不到,索引白建且不易察觉
  • 以为函数索引能被所有语句复用:覆盖索引能力弱于普通索引,DML 与部分聚合场景仍受限
  • 在 MySQL 8.0.13 之前的版本尝试该语法:直接报语法错误,升级前需确认版本
  • 对区分度低的表达式滥用函数索引:建索引前先评估选择性与实际扫描行数,否则索引维护成本白付

数据来源与参考

  • MySQL 8.0 Reference Manual: Functional Indexes

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌