json_array混用NULL与空字符串易致类型混淆,json_object键名不可为列名,嵌套需防多行与层级过深,版本差异及性能问题须提前规避。

JSON_ARRAY 里混用 NULL 和空字符串容易踩坑
mysql 的 JSON_ARRAY 会把 SQL NULL 直接转成 JSON null,但空字符串 '' 会保留为 "" —— 看似一样,实际类型不同,前端解析或后续 JSON 函数(比如 JSON_CONTAINS)可能行为不一致。
常见错误现象:JSON_ARRAY(col1, col2) 返回 [null, ""],结果被当成两个有效值处理,导致统计或条件判断出错。
- 明确区分意图:真缺失用
NULL,空内容用'';别依赖默认转换 - 需要统一为空值时,提前用
NULLIF(col, '')过滤掉空字符串 - 如果字段可能为
NULL且你不想它出现在数组里,得用CASE WHEN col IS NOT NULL THEN col END做条件包裹,再进JSON_ARRAY
JSON_OBJECT 键名必须是字符串字面量或表达式,不能是列别名
写 JSON_OBJECT(key_col, value_col) 是错的 —— key_col 是列名,不是字符串,MySQL 会报错 Incorrect parameters in the call to native function 'JSON_OBJECT'。
使用场景:动态构造键值对,比如把表中两列转成对象,但键名本身来自数据(如配置表的 setting_key 和 setting_value)。
- 正确做法是用
JSON_OBJECTAGG配合GROUP BY,它专为“列转键值”设计 - 如果非要用
JSON_OBJECT,键必须显式写死或用字符串表达式,例如JSON_OBJECT('name', name, 'id', id) - 想从列值生成键?只能拼接 +
JSON_SET或用应用层处理,SQL 层做不到运行时动态键名
嵌套 JSON_ARRAY 和 JSON_OBJECT 时要注意括号层级和类型校验
嵌套太深或类型不匹配会导致 Invalid JSON text 错误,尤其在子查询返回多行时,JSON_ARRAY(select ...) 会直接失败 —— 它只接受标量或单值表达式,不支持多行结果集。
性能影响:每层 JSON 构造都触发一次序列化,嵌套超过 3–4 层时,CPU 开销明显上升,特别是配合大字段或 GROUP_CONCAT 使用时。
- 多行聚合用
JSON_ARRAYAGG,不是JSON_ARRAY;后者只拼几个已知值 - 嵌套前先确认子表达式是否返回单值:比如
(SELECT MAX(price) FROM items WHERE order_id = o.id)可以,(SELECT price FROM items WHERE order_id = o.id)不行 - 调试技巧:先把子表达式单独执行,看结果是不是合法 JSON 字符串;不是就加
CAST(... AS JSON)强制转换
MySQL 5.7 和 8.0 在 JSON 函数行为上存在隐性差异
5.7 的 JSON_OBJECT 不支持重复键去重,8.0 默认保留最后一个;5.7 对非法 UTF-8 字符更宽容,8.0 直接报错 Invalid utf8mb4 character。
兼容性影响:同一段 SQL 在两个版本可能返回不同结构,甚至执行失败,线上迁移时容易漏测。
- 建表时对 JSON 字段加
GENERATED column并设STORED,比运行时拼接更稳定 - 避免在
WHERE子句里用JSON_EXTRACT(JSON_OBJECT(...), '$.x')做条件,索引无法生效,5.7 尤其慢 - 跨版本部署前,用
SELECT VERSION()检查,并在测试库跑EXPLAIN format=JSON看 JSON 函数是否被下推
最麻烦的其实是混合了业务逻辑和 JSON 结构的查询 —— 比如一边要按时间排序,一边还要把关联的标签数组塞进 JSON_OBJECT 里。这时候函数嵌套、NULL 处理、版本兼容全撞在一起,单靠 SQL 很难干净解决。