用一个对列做了UPPER()运算的WHERE条件,对比普通索引和函数索引,看为什么'索引存在'不等于'索引能用'。
note列本身是普通字符串,查询条件里对它做了UPPER()大写转换。
代码SELECT * FROM orders WHERE UPPER(note) = 'ORDER-NOTE-500-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
-> 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)));
-> Index lookup on orders using idx_upper_note (upper(note) = 'ORDER-NOTE-500-...') (cost=0.35 rows=1)
没有函数索引(全表扫描): 16423 微秒 (约16.4毫秒) 有函数索引((UPPER(note))): 220 微秒 (约0.22毫秒) ← 快约75倍
这75倍的差距不是索引本身有多神奇,而是全表扫描和索引查找在数据量上的天然差异:前者要读完全部5万行再逐行计算UPPER()比较,后者直接在索引树里做一次精确查找。函数索引真实解决的问题是"业务逻辑里天然存在对列做函数运算的查询",比如不区分大小写搜索、按日期部分分组、JSON字段里取某个key的值——只要这类查询频繁出现,给对应的函数表达式建一个函数索引,比每次都全表扫描划算得多。
每一步都是本文上面用EXPLAIN FORMAT=TREE和真实计时跑出来的原始结果。
CREATE INDEX ... ((expr)):MySQL 8.0.13起真实支持的函数索引语法