必须用stored生成列提取json值并显式声明类型(如cast(... as char))后建索引,查询时须直接引用该列名,否则无法走索引。

MySQL 8.0中JSON列无法直接建索引,必须用虚拟列中转
MySQL 不允许对 JSON 类型字段直接创建索引,哪怕只是 JSON_EXTRACT() 的简单路径。你得先定义一个 **生成列(generated column)**,把目标值“抽出来”,再给它加索引。这个列必须是 STORED(不能是 VIRTUAL),因为只有 STORED 列支持索引——虽然名字叫“虚拟列”,但 MySQL 文档里常把 GENERATED ALWAYS AS 列统称 virtual column,实际建索引时必须选 STORED。
正确写法:用 CAST(... AS ...) 显式转类型,别信 JSON_EXTRACT() 返回值能自动适配
很多人卡在索引建不成功,根本原因是没处理好数据类型。比如想索引 data->'$.name',JSON_EXTRACT() 返回的是带引号的 JSON 字符串(如 "Alice"),不是普通 VARCHAR。必须用 CAST 剥掉双引号:
ALTER TABLE users
ADD COLUMN name_str VARCHAR(100)
GENERATED ALWAYS AS (CAST(data->>'$.name' AS CHAR(100))) STORED;
注意这里用了 ->>(即 JSON_UNQUOTE(JSON_EXTRACT())),比单独用 -> 更安全;CAST(... AS CHAR(N)) 是必须的,AS VARCHAR(N) 在某些旧版本会报错,CHAR 更稳。
-
->和->>行为不同:->返回带引号字符串,->>自动去引号,更适合做索引源 - 长度设太小会截断,设太大(如
CHAR(255))不影响性能,但要和实际业务值匹配 - 如果抽的是数字,用
CAST(... AS SIGNED)或CAST(... AS DECIMAL(10,2)),别用INT(MySQL 不认这个类型名)
添加索引前确认虚拟列已持久化且非空,否则 CREATE INDEX 会静默失败
执行 ADD COLUMN ... STORED 后,MySQL 会立即计算并存储所有行的值。但如果某行的 data 里根本没有 $.name 字段,该列值就是 NULL。而 NULL 值默认可以被索引(B+Tree 支持),但如果你后续 WHERE name_str = 'Alice',这条语句不会命中 NULL 行——这本身没问题。真正容易踩坑的是:如果你加了 NOT NULL 约束但数据里存在缺失,ALTER TABLE 会直接报错中断。
- 检查缺失情况:
SELECT COUNT(*) FROM users WHERE data->>'$.name' IS NULL; - 如果允许
NULL,建索引就直接上:CREATE INDEX idx_name_str ON users(name_str); - 如果业务要求非空,得先补数据或改生成表达式,例如用
COALESCE(data->>'$.name', 'unknown') - 别用
INDEX关键字代替KEY——两者等价,但显式写KEY更符合 MySQL DDL 习惯
查询时必须用生成列名,优化器才可能走索引
加完索引不代表查询自动加速。你得在 WHERE 条件里明确引用那个生成列,而不是再去写 data->>'$.name':
-- ✅ 走索引 SELECT * FROM users WHERE name_str = 'Alice'; <p>-- ❌ 不走索引(即使表达式一模一样) SELECT * FROM users WHERE data->>'$.name' = 'Alice';</p>
MySQL 优化器目前不支持“表达式等价推导”,它只认列名。另外,如果查询同时涉及多个 JSON 字段,每个都得单独建生成列+索引,没法“一列多用”。
最常被忽略的一点:生成列的字符集和排序规则必须和查询条件一致。比如你的 name_str 是 utf8mb4_0900_as_cs,但 WHERE name_str = 'Alice' 字符串默认用连接字符集,万一不匹配,索引可能失效。建议建表时统一用 utf8mb4 及其默认 collation,别手动指定冷门 collation。











