JSON字段本身不能建有效索引,必须通过虚拟列提取路径值(如info->>"$.status")并为其创建普通索引,否则JSON路径查询将全表扫描;PostgreSQL则可用JSONB配合GIN索引或表达式索引高效查询。

直接结论:JSON字段本身不能建有效索引,必须通过虚拟列+路径提取+普通索引三步走,否则WHERE data->'$.status' = 'active'永远走全表扫描。
MySQL中为什么不能直接对JSON字段建索引
执行CREATE INDEX idx_data ON users(info)看似合法,但实际无效——MySQL 5.7+虽允许该语法,但该索引仅用于判断info IS NULL或JSON_VALID(info)这类元信息,无法加速info->'$.name'这类路径查询。真正生效的索引必须绑定到具体提取后的值上。
用虚拟列提取关键路径并建索引(MySQL 5.7+)
这是最通用、兼容性最好的方案,核心是把JSON路径结果“物化”为一个可索引的列:
- 虚拟列定义必须用
STORED(不是VIRTUAL),否则无法建索引 - 表达式必须是确定性的,例如
JSON_UNQUOTE(JSON_EXTRACT(info, '$.status'))或简写info->>'$.status' - 建索引时直接对虚拟列名操作,和普通字段无异
示例:
ALTER TABLE users ADD COLUMN status VARCHAR(20) STORED AS (info->>'$.status'), ADD INDEX idx_status (status);
之后即可高效执行:SELECT * FROM users WHERE status = 'active';
PostgreSQL中优先用JSONB + GIN索引
PostgreSQL原生支持更成熟的JSONB类型,无需虚拟列也能高效查询:
- 用
JSONB类型代替JSON,自动去重、排序、压缩,且支持GIN索引 - 建GIN索引后,
data @> '{"status":"active"}'或data->>'status' = 'active'均可命中索引 - 若只查单个字段,也可用表达式索引:
CREATE INDEX idx_status ON users ((data->>'status'));
注意:@>是包含操作符,适合匹配子对象;->>取text值更适合等值查询。
WHERE条件里别写错JSON路径语法
常见错误会直接导致索引失效或查不到数据:
- 路径字符串必须用单引号包裹,且路径内双引号要转义或用单引号嵌套:
info->>'$.user.name'✔️,info->>"$.user.name"❌ - 数组下标从0开始,
info->>'$[0].tags[1]'才能取第一个对象的第二个tag - 用
->返回JSON类型结果(带引号),->>返回去引号的标量值;多数等值比较场景应选->> - 避免在WHERE里用
JSON_CONTAINS()或JSON_SEARCH()做主过滤条件——它们不走索引
最易被忽略的一点:虚拟列一旦定义,其表达式就固化为表结构的一部分;后续如果JSON结构变更(比如$.status变成$.meta.status),必须手动DROP COLUMN再重建,不能靠ORM自动迁移。


















