json_extract()返回带双引号的json字符串,需用json_unquote()或cast转换才能正常比较;路径错误、数据非法或字段非json类型均静默返回null;高频查询应建虚拟列+索引。

直接说结论:JSON_EXTRACT()能提取值,但返回的是带双引号的 JSON 类型字符串;想当普通字段用,必须配 JSON_UNQUOTE() 或类型转换,否则 = 'xxx' 会永远不匹配。
为什么 JSON_EXTRACT() 返回的值总对不上?
它不是返回字符串 'admin',而是返回 JSON 片段 ""admin""(即带引号的字符串)。在 WHERE 或 JOIN 中直接写 JSON_EXTRACT(data, '$.role') = 'admin',实际是在比 ""admin"" = 'admin' —— 永远为 false。
- 正确做法是:
JSON_UNQUOTE(JSON_EXTRACT(data, '$.role')) = 'admin' - 数字字段也一样:
CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) > 18 - 布尔值别信直觉:
JSON_EXTRACT(data, '$.active') = true可用于条件,但视图或变量赋值建议转成CAST(... AS UNSIGNED) - 路径写错、key 不存在、字段内容非法(非 JSON)都会静默返回
NULL,不会报错
路径怎么写才不翻车?
路径语法看着像 JS,但 MySQL 对符号和边界极其敏感,错一个字符就 NULL,且无提示。
- 对象属性必须用
$.key,不要用$['key'](虽合法但无法拼接变量,易出错) - 数组下标从 0 开始:
$.items[0].price是第一个,$.items[1]是第二个 - 含空格或点的 key 必须加双引号:
$."full name"或$.user."last.login",写成$.user.last.login就失效 - 反斜杠要双写:
$.path."new\old",单写会被当转义符吃掉 - 不确定字段是否存在?先用
JSON_CONTAINS_PATH(data, 'one', '$.email')判断路径存在性,再提取
WHERE 里用 JSON_EXTRACT() 为什么慢得像卡住?
因为它根本没法走索引——每次执行都要全表读取、完整解析 JSON 字段、按路径提取、再过滤。千万级表上,WHERE JSON_EXTRACT(data, '$.status') = 'done' 很可能跑 5 秒以上。
- 别指望函数索引救场:
CREATE INDEX idx_status ON t (JSON_EXTRACT(data, '$.status'))仅对等值查询有效,>、BETWEEN、LIKE依然全表扫 - 真要高频查某个嵌套字段?建虚拟列:
ALTER TABLE t ADD COLUMN status VARCHAR(20) AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.status'))) STORED;,再CREATE INDEX idx_status ON t(status); - 数组通配路径(如
$.tags[*].id)不支持虚拟列,这时只能用JSON_TABLE()展开后查,或预处理到关系型字段 - 用
EXPLAIN FORMAT=TREE看执行计划,如果出现"table": "t", "rows": 1000000,说明已放弃索引
字段不是 JSON 类型,还能用 JSON_EXTRACT() 吗?
可以,但风险极高。MySQL 会尝试隐式解析,只要内容非法(比如单引号代替双引号、中文引号、尾部逗号、未转义换行),就直接返回 NULL,还不告诉你哪错了。
- 务必先校验:
SELECT data, JSON_VALID(data) FROM t WHERE id = 123;返回1才真正可安全提取 - TEXT/VARCHAR 字段入库时没做校验,很可能混入脏数据;而 JSON 类型字段在 INSERT/UPDATE 时已强制校验,更可靠
- 视图里用
JSON_EXTRACT()更危险:创建成功,查的时候才发现全是NULL,因为路径写错或数据结构不一致 - 上线前一定要用真实数据跑一遍:
SELECT id, JSON_EXTRACT(data, '$.xxx') FROM t LIMIT 10;看是否真有值
最常被忽略的一点:JSON 字段本身不索引,所有基于它的查询都是计算密集型操作。哪怕你只查 10 行,只要路径表达式动态拼接、或用了通配符、或字段内容不规范,性能就会断崖下跌——这不是调优能解决的,是设计层面的约束。











