json_contains仅支持存在性判断,不能提取字段值;where中用json_extract会导致全表扫描;json_table需顶层使用;->返回带引号字符串,->>返回纯文本。

JSON_CONTAINS 能不能查嵌套对象里的字段值?
不能直接当“字段提取器”用,但能做存在性判断。比如 JSON_CONTAINS(profile, '"篮球"', '$.hobbies') 是合法的,它检查 $.hobbies 这个路径下是否包含字符串 "篮球";但如果你写成 JSON_CONTAINS(profile, 'true', '$.notification.email'),前提是 profile 里该路径确实存的是 JSON 布尔字面量 true(不是字符串 "true"),否则匹配失败。
常见翻车点:
- candidate 参数必须和目标值类型一致:
true≠"true",123≠"123" - 路径写错(比如漏掉
$或多加引号)会导致整个函数返回NULL,WHERE 条件恒假,查不到数据也不报错 - 对数组元素做精确匹配时,
JSON_CONTAINS(arr, '"foo"')只要数组里有一个"foo"就为真,不保证唯一或位置
为什么 WHERE 里用 JSON_EXTRACT 会变慢?
因为 JSON_EXTRACT() 在 WHERE 子句中触发的是“全表扫描 + 解析 + 提取”,不是索引查找。MySQL 无法为 JSON_EXTRACT(data, '$.user.address.city') 这类表达式自动建 B+ 树索引,每次查询都要把整列 JSON 字段读出来、解析成内存结构、再按路径取值,最后比对——千万级表上很容易超秒。
真正安全的用法只有两种:
- 出现在
SELECT列表里,比如SELECT id, JSON_EXTRACT(data, '$.status') FROM t WHERE id = 123(主键已筛选) - 配合虚拟列 + 索引:先
ALTER TABLE t ADD COLUMN city VARCHAR(50) AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.user.address.city'))) STORED,再CREATE INDEX idx_city ON t(city)
深层嵌套数组怎么展开成行?用 JSON_TABLE 而不是子查询
JSON_TABLE 必须放在顶层 FROM 或 JOIN 中,不能塞进子查询的 FROM (...) 里——否则会报 ERROR 1248。要处理像 {"user": {"id": 1, "orders": [{"amt": 100}, {"amt": 200}]}} 这种结构,得用两层 JSON_TABLE 配合 JOIN:
SELECT u.id, o.amt
FROM orders_main
JOIN JSON_TABLE(
data, '$' COLUMNS (
id INT PATH '$.user.id',
orders JSON PATH '$.user.orders'
)
) AS u ON TRUE
JOIN JSON_TABLE(
u.orders, '$[*]' COLUMNS (amt DECIMAL PATH '$.amt')
) AS o ON TRUE;
注意点:
- 第一层路径用
'$'提取根级字段,把数组保留为JSON类型传给第二层 - 第二层输入是上一层的列(
u.orders),路径用'$[*]'展开数组 -
ON TRUE是必需的,因为这不是传统等值连接,而是“横向展开”语义
-> 和 ->> 的区别到底影响什么?
-> 返回带双引号的 JSON 字符串(如 "beijing"),->> 自动调用 JSON_UNQUOTE 返回纯文本(beijing)。这个差异在 WHERE 条件里特别关键:
- 用
WHERE detail->'$.techparams.ownership' = '"top"'是错的——左边是"top",右边也是"top",但你实际想比的是值,不是 JSON 字符串 - 正确写法是
WHERE detail->>'$.techparams.ownership' = 'top',或者用JSON_EXTRACT(detail, '$.techparams.ownership')配合JSON_UNQUOTE - 如果字段可能缺失,
->>会返回NULL,而->返回NULL(不是"null"),这点在IS NULL判断时行为一致
路径里含空格或特殊字符的 key(比如 "full name")必须写成 $.user."full name",写成 $.user.full name 会静默失败,返回 NULL。











