JSON_MERGE_PATCH()仅支持两个参数,因其严格遵循RFC 7396标准,语义定义为“一个源文档+一个补丁”,不支持多文档归并;传入三个及以上参数会报Incorrect parameter count错误。

直接用 JSON_MERGE_PATCH() 两两合并,不能一次传三个及以上参数——它只接受两个参数,多层合并必须链式调用。
为什么 JSON_MERGE_PATCH() 不支持多个参数?
MySQL 的 JSON_MERGE_PATCH() 函数签名固定为 JSON_MERGE_PATCH(doc, patch),官方未提供变长参数支持。试图写成 JSON_MERGE_PATCH(a, b, c) 会报错 Incorrect parameter count。
常见错误现象:在构建用户配置更新逻辑时,误以为能“一次性合并默认配置 + 租户配置 + 用户覆盖”,结果 SQL 直接执行失败。
- 函数设计遵循 RFC 7396,语义上就是「一个源文档 + 一个补丁」,不是「多文档归并」
- MySQL 8.0.22+ 虽支持 PREPARE 中安全绑定 patch 参数,但依然只认两个位置占位符
- 想绕过限制用
CONCAT()拼 JSON 字符串?风险极高:一旦中间 patch 含NULL、单引号或未转义反斜杠,整个 JSON 解析失败
正确做法:链式调用 + IFNULL 和 COALESCE 防空
合并三层配置(如:系统默认 → 租户定制 → 用户覆盖)时,必须分步进行,且每步都要处理可能的 NULL 值——否则任一环节为 NULL,整条链返回 NULL。
实操建议:
- 左侧始终用
IFNULL(col, '{}')包裹主文档,避免因字段为空导致整行失效 - 右侧 patch 统一用
COALESCE(?, '{}'),防止应用层传NULL或空字符串崩掉函数 - 多层合并按从左到右顺序:先合并默认与租户,再把结果与用户 patch 合并
示例(安全更新用户最终配置):
UPDATE users u
JOIN tenants t ON u.tenant_id = t.id
SET u.config = JSON_MERGE_PATCH(
JSON_MERGE_PATCH(
IFNULL(t.default_config, '{}'),
IFNULL(t.tenant_config, '{}')
),
COALESCE(u.user_override, '{}')
)
WHERE u.id = ?;
数组字段被意外清空?检查你是不是该用 JSON_MERGE_PRESERVE()
JSON_MERGE_PATCH() 对数组是「完全替换」,不是「追加」。比如 JSON_MERGE_PATCH('{"tags": ["a"]}', '{"tags": ["b"]}') → {"tags": ["b"]},原始 "a" 消失了。
这在配置合并中极易踩坑:
- 你想让「用户标签」继承租户默认标签再叠加自己的,就得改用
JSON_MERGE_PRESERVE() - 或者手动提取数组、用
JSON_ARRAY_APPEND()拼接,再塞回对象 - 别依赖
JSON_SET()替代合并——它不处理嵌套缺失路径,也不自动创建中间对象
关键区别速查:
-
JSON_MERGE_PATCH('{ "a": [1] }', '{ "a": [2] }')→{"a": [2]} -
JSON_MERGE_PRESERVE('{ "a": [1] }', '{ "a": [2] }')→{"a": [1, 2]}
性能与兼容性注意点
链式调用 JSON_MERGE_PATCH() 本身开销不大,但要注意底层 JSON 二进制解析成本随文档深度/大小上升。真正容易被忽略的是生成列索引和条件查询的脱节:
- 如果你对
config->'$.theme'建了生成列索引,但更新时用的是JSON_MERGE_PATCH(),MySQL 能走索引;但如果中间某次 patch 是NULL导致整字段变空,索引值就失效了 - MariaDB 10.6+ 支持
JSON_MERGE_PATCH(),但不支持 PREPARE 中参数化 patch,应用层需自行拼接并严格校验 JSON 合法性 - 8.0.17+ 的多值索引(
CREATE INDEX idx_tags ON t ((CAST(config->'$.tags' AS CHAR(1000) ARRAY))))对 patch 后的数组生效,但前提是 patch 操作没把整个$.tags覆盖成非数组类型
最常漏掉的一点:合并后没触发生成列自动更新——如果配置字段是 JSON 类型,而你靠 JSON_EXTRACT() 提取值建了虚拟列,那 JSON_MERGE_PATCH() 更新主字段时,虚拟列确实会同步刷新;但若用的是旧版 MySQL 或手写的触发器逻辑,得确认是否真更新了。


















