MySQL 5.7+ 不能直接对 JSON 字段建索引,须通过虚拟列提取标量值(如 CAST(JSON_EXTRACT(data, '$.status') AS CHAR(20)))并建索引。

MySQL 5.7+ 怎么给 JSON 字段加索引
直接说结论:不能对 JSON 类型字段本身建普通索引,必须提取出具体路径的标量值,再通过虚拟列(generated column)中转,最后在该列上建索引。
这是因为 JSON 是半结构化类型,B+ 树索引需要确定长度和可比较性,而原生 JSON 值不满足——MySQL 会报错:JSON column 'xxx' cannot be used in key specification。
- 必须用
JSON_EXTRACT()或简写操作符->/->>提取字符串、数字等基础类型 - 虚拟列需声明为
STORED或VIRTUAL;但只有STORED列支持全文索引,VIRTUAL更省空间且足够用于普通查询索引 - 提取后字段要显式转类型,比如
CAST(JSON_EXTRACT(data, '$.status') AS CHAR(20)),否则可能因隐式转换导致索引失效
PostgreSQL 怎么在 jsonb 字段上高效查 key 和 value
PostgreSQL 的 jsonb 支持 GIN 索引,但不同查询模式对应不同索引策略,不是建一个就全包了。
常见误操作是只建 CREATE INDEX ON tbl USING GIN (data),结果发现 WHERE data @> '{"status":"active"}' 快,但 WHERE data->>'status' = 'active' 依然走全表扫描——因为后者需要路径表达式索引。
- 查整个子对象是否存在(如
@>)、键是否存在(?),用默认GIN索引即可 - 查某个固定路径的值(如
data->>'status'),得建函数索引:CREATE INDEX ON tbl ((data->>'status')) - 如果路径值有大量 NULL 或空字符串,考虑加
WHERE data->>'status' IS NOT NULL条件建部分索引,减少索引体积
MySQL 多值索引(Multi-Value Index)怎么用才生效
MySQL 8.0.17+ 支持对 JSON 数组字段建多值索引,但仅限于 MEMBER OF()、JSON_CONTAINS()、JSON_OVERLAPS() 这三类查询能命中,其他写法一律无效。
典型坑是以为 WHERE '"tag1"' MEMBER OF(data->'$.tags') 能走索引,结果发现没生效——问题出在左边是字符串字面量,右边是 JSON 路径,类型不匹配。MySQL 要求左侧必须是 JSON 值,正确写法是 WHERE JSON_EXTRACT('["tag1"]', '$[0]') MEMBER OF(data->'$.tags'),更常用的是直接写 WHERE '"tag1"' MEMBER OF(data->'$.tags')(注意引号嵌套)。
- 多值索引定义必须用
CAST(... AS JSON ARRAY)或明确的 JSON 数组字面量,例如:CREATE INDEX idx_tags ON t ((CAST(data->'$.tags' AS JSON ARRAY))) - 只支持
JSON_ARRAY类型字段或路径,不支持JSON_OBJECT或标量值 - 执行计划里看到
Using index condition; Using where with pushed condition才算真正用了多值索引
虚拟列索引为什么有时不走、有时慢
虚拟列本身不存数据,每次读都要计算;索引虽建了,但优化器可能因统计信息不准、谓词写法不匹配或隐式转换放弃使用它。
最常被忽略的一点:虚拟列的表达式必须和查询中出现的表达式**完全一致**,连空格、括号顺序都不能差。比如虚拟列定义是 CAST(JSON_EXTRACT(data, '$.id') AS UNSIGNED),但查询写了 CAST(JSON_EXTRACT(data, '$.id') AS SIGNED),索引就废了。
- 检查是否启用
optimizer_switch='use_index_extensions=on'(MySQL 默认开,但某些旧版本或配置可能关) - 用
EXPLAIN FORMAT=TREE查看是否出现usable_key_parts,没有说明优化器没选上该索引 - 虚拟列若含函数调用(如
LOWER()、TRIM()),注意字符集和排序规则是否一致,否则可能触发隐式转换
EXPLAIN 验证。










