mysql 8.0版本新增的函数索引特性,支持对JSON字段中的具体路径值创建索引,从而大幅提升涉及JSON字段的查询性能。传统方式中直接对JSON字段建普通索引无法生效,函数索引则通过提取JSON路径的标量值来构建索引结构,完美解决了这一痛点。
JSON字段索引的痛点
JSON类型字段在mysql中存储的是非结构化数据,普通B树索引无法直接对JSON整体内容生效。如果业务中经常需要根据JSON字段内部的某个属性查询数据,比如用户表的扩展信息字段ext_info中存储了用户的城市编码,查询某个城市的所有用户时,全表扫描会导致性能严重下降。
mysql 8.0函数索引原理
函数索引允许对表达式的计算结果创建索引,针对JSON字段,我们可以通过JSON_EXTRACT函数提取指定路径的标量值,再基于这个提取结果创建索引。mysql会将函数表达式的计算结果持久化到索引中,查询时如果匹配到相同的表达式,就可以直接走索引查询,避免全表扫描。
支持的JSON提取函数
JSON_EXTRACT(json_doc, path):提取JSON文档中指定路径的值,返回JSON类型结果->:JSON_EXTRACT的简写形式,比如ext_info->'$.city_code'->>:提取后返回字符串类型,等价于JSON_UNQUOTE(JSON_EXTRACT(...))
创建JSON字段函数索引步骤
1. 准备测试表结构
首先创建一个包含JSON字段的用户表,插入测试数据:
-- 创建用户表,ext_info为JSON类型字段
CREATE TABLE user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
ext_info JSON
);
-- 插入测试数据
INSERT INTO user_info (username, ext_info) VALUES
('张三', '{"city_code": "110100", "age": 25}'),
('李四', '{"city_code": "310100", "age": 30}'),
('王五', '{"city_code": "110100", "age": 28}'),
('赵六', '{"city_code": "440100", "age": 35}');
2. 创建函数索引
假设我们需要频繁查询ext_info中city_code为某个值的所有用户,就可以为JSON_EXTRACT(ext_info, '$.city_code')创建函数索引:
-- 为JSON字段的city_code路径创建函数索引 CREATE INDEX idx_ext_info_city_code ON user_info ((JSON_EXTRACT(ext_info, '$.city_code')));
也可以使用->简写形式创建,效果完全一致:
CREATE INDEX idx_ext_info_city_code ON user_info ((ext_info->'$.city_code'));
3. 验证索引生效情况
使用EXPLAIN语句分析查询计划,查看索引是否被使用:
-- 查询北京的用户,查看执行计划 EXPLAIN SELECT * FROM user_info WHERE JSON_EXTRACT(ext_info, '$.city_code') = '110100';
执行结果中如果key列显示idx_ext_info_city_code,说明索引已经生效。如果使用->>提取字符串值查询,需要对应创建匹配的函数索引:
-- 创建返回字符串类型的函数索引 CREATE INDEX idx_ext_info_city_code_str ON user_info ((ext_info->>'$.city_code')); -- 该查询会使用上面的索引 EXPLAIN SELECT * FROM user_info WHERE ext_info->>'$.city_code' = '110100';
注意事项
- 函数索引的表达式必须和查询中的表达式完全一致才会生效,比如创建的是
JSON_EXTRACT索引,查询时用->>可能无法匹配索引 - 函数索引会占用额外的存储空间,需要根据实际查询频率权衡是否创建
- mysql 5.7版本不支持函数索引,只能使用虚拟列的方式间接为JSON字段创建索引,8.0版本的函数索引是更简洁的方案
- 如果JSON路径不存在对应值,提取结果为NULL,NULL值不会存入普通索引,因此查询NULL值无法使用该函数索引
虚拟列方案对比(兼容低版本)
如果是mysql 5.7版本,可以通过添加虚拟列再建索引的方式实现类似效果:
-- 添加虚拟列,提取city_code ALTER TABLE user_info ADD COLUMN city_code VARCHAR(20) GENERATED ALWAYS AS (ext_info->>'$.city_code') VIRTUAL; -- 为虚拟列创建索引 CREATE INDEX idx_city_code ON user_info (city_code); -- 查询时使用虚拟列即可走索引 SELECT * FROM user_info WHERE city_code = '110100';
这种方式相比8.0的函数索引步骤更繁琐,且会修改表结构,优先推荐使用8.0的函数索引方案。