MySQL无法直接对JSON字段建索引,必须通过生成列固化路径提取值后再建索引;虚拟列需用->>提取标量、类型匹配、显式处理NULL,且查询必须改写为引用虚拟列名才能命中索引。

不能直接对 JSON 字段建索引,否则查询必走全表扫描——这是 MySQL 的硬限制,不是配置或语法问题。
为什么 CREATE INDEX idx ON t(json_col->>'$.field') 会报错或无效
MySQL 在 8.0.13 之前完全不支持函数索引;即使在 8.0.13+,CREATE INDEX 语句中也不允许直接写路径表达式。执行该语句会触发 ERROR 3152: JSON column cannot be used in key specification。
更隐蔽的问题是:即使你绕过语法检查(比如用虚拟列表达式拼进 CREATE INDEX),优化器也无法将 WHERE json_col->>'$.field' = 'x' 自动重写为命中索引——它只认列名,不认表达式。
-
->返回带双引号的 JSON 类型值(如"alice"),而->>才返回去引号的标量值(如alice) - 虚拟列类型必须严格匹配提取值的实际类型:字符串用
VARCHAR(N),整数用INT或UNSIGNED INT,布尔用TINYINT(1) - 若字段可能为
NULL,虚拟列定义里必须显式写NULL,否则插入含缺失字段的 JSON 会失败
MySQL 5.7 和 8.0+ 都必须用生成列,但写法有关键差异
生成列是唯一兼容 5.7 和 8.0+ 的方案,核心是先固化路径提取结果,再对这个列建普通 B+Tree 索引。
以 properties JSON 字段中提取 $.request.email 为例:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
ALTER TABLE activity_log ADD COLUMN email_str VARCHAR(255) GENERATED ALWAYS AS (properties->>'$.request.email') STORED NOT NULL, ADD INDEX idx_email_str (email_str);
- 必须用
->>,不是->;否则存的是"alice@domain.com",查'alice@domain.com'永远不命中 - 推荐用
STORED(物理存储),避免VIRTUAL列在某些查询路径下被优化器跳过 -
NOT NULL要慎加:如果部分 JSON 缺少request.email,加了NOT NULL会导致 INSERT 失败;此时应去掉NOT NULL,并在建索引时接受NULL值
查询时必须改写 WHERE 条件,否则索引不生效
建完索引后,原始查询 WHERE properties->>'$.request.email' = 'x@y.z' 依然全表扫描——优化器不会自动替换表达式。
你必须把条件改成虚拟列名:
SELECT * FROM activity_log WHERE email_str = 'x@y.z';
- 支持
=、IN、LIKE 'prefix%'(前提是虚拟列类型足够宽,且没用前导通配符) - 不支持
JSON_CONTAINS(properties, '"x@y.z"', '$.request.email')或JSON_SEARCH()—— 这些函数无法利用任何索引 - 如果业务代码大量使用原始 JSON 路径查询,需要同步改造 SQL,否则索引形同虚设
MySQL 8.0.17+ 多值索引只适用于 JSON 数组场景
如果你要查的是 JSON 数组里的某个值(比如 {"tags": ["mysql", "json", "index"]}),且想查 "json" IN tags,才需要多值索引。
语法必须带 CAST(... AS UNSIGNED ARRAY) 或 CAST(... AS CHAR(32) ARRAY):
ALTER TABLE posts ADD COLUMN tag_array CHAR(32) GENERATED ALWAYS AS (tags) STORED, ADD INDEX idx_tags ((CAST(tag_array AS CHAR(32) ARRAY)));
- 仅限
JSON字段中存储的是纯标量数组(不能混对象或嵌套) - 只能配合
MEMBER OF()、JSON_CONTAINS()、JSON_OVERLAPS()使用,普通=不走这个索引 - 多值索引本质是为每个数组元素生成一条索引记录,空间开销比单值索引大得多
真正容易被忽略的点是:虚拟列定义和查询条件必须严格一致——类型、NULL 性、路径表达式、甚至空格位置都影响索引是否被选用。一旦某条数据因 JSON 结构异常导致虚拟列值为 NULL,而你又用了 NOT NULL 约束,整条 INSERT 就会失败,而不是静默跳过。

















