bigywater.tech
MySQL / InnoDB · 46

函数索引

用一个对列做了UPPER()运算的WHERE条件,对比普通索引和函数索引,看为什么'索引存在'不等于'索引能用'。

75倍
函数索引对全表扫描的真实提速
0.35
函数索引Index lookup的真实EXPLAIN代价估算

做的事:WHERE条件对列做了函数运算

note列本身是普通字符串,查询条件里对它做了UPPER()大写转换。

代码SELECT * FROM orders WHERE UPPER(note) = 'ORDER-NOTE-500-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

验证:note列上的普通索引,真实完全用不上

实测 · 本机EXPLAIN FORMAT=TREE输出(note列已有普通索引idx_note_plain)
-> Filter: (upper(orders.note) = 'ORDER-NOTE-500-...')
    -> Table scan on orders   ← 完全没用上idx_note_plain

即使note列上已经真实存在一个普通索引,只要查询条件是UPPER(note) = ...而不是note = ...,MySQL就没法直接用这个索引——索引里存的是note原始值的顺序,跟UPPER()转换之后的值顺序对不上,普通索引在这种"列被函数包裹"的条件面前真实完全失效,退化成全表扫描。

验证:建一个函数索引,条件里的函数运算和索引定义对上了

代码CREATE INDEX idx_upper_note ON orders((UPPER(note)));
实测 · 本机EXPLAIN FORMAT=TREE输出
-> Index lookup on orders using idx_upper_note (upper(note) = 'ORDER-NOTE-500-...')  (cost=0.35 rows=1)
函数索引真实存的不是note的原始值,而是UPPER(note)计算之后的结果——相当于MySQL在后台维护了一个隐藏的生成列,每次INSERT/UPDATE都会真实重新算一遍UPPER(note)并更新进索引。查询条件里的UPPER(note) = ...和索引定义里的表达式((UPPER(note)))字面匹配上了,才能真实走索引查找,执行计划从Table scan变成了Index lookup,估算行数从近5万行直接降到1行。

验证:真实耗时,从16.4毫秒到0.22毫秒

实测 · 本机执行耗时(同样查到1行)
没有函数索引(全表扫描):   16423 微秒 (约16.4毫秒)
有函数索引((UPPER(note))):    220 微秒 (约0.22毫秒)   ← 快约75倍

这75倍的差距不是索引本身有多神奇,而是全表扫描和索引查找在数据量上的天然差异:前者要读完全部5万行再逐行计算UPPER()比较,后者直接在索引树里做一次精确查找。函数索引真实解决的问题是"业务逻辑里天然存在对列做函数运算的查询",比如不区分大小写搜索、按日期部分分组、JSON字段里取某个key的值——只要这类查询频繁出现,给对应的函数表达式建一个函数索引,比每次都全表扫描划算得多。

分步演示:普通索引 vs 函数索引,对UPPER()条件的处理

每一步都是本文上面用EXPLAIN FORMAT=TREE和真实计时跑出来的原始结果。

函数索引:只有表达式字面匹配才能命中
这一步新验证的索引类型

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),5万行orders,note列上分别建普通索引和函数索引对比
    • CREATE INDEX ... ((expr)):MySQL 8.0.13起真实支持的函数索引语法
    京ICP备2026067502号-1京公网安备11010502063487号