登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL JSON 部分更新什么时候真的生效

来源:17golang原创

时间:2026-10-20 23:02:39 132浏览 收藏

我第一次把大字段拆成 MySQL JSON 列时,最容易误判的一点是:看到 JSON_SET() 就以为数据库一定只改动 JSON 文档的一小段。实际更准确的说法是,JSON_SET() 让这条更新具备“部分更新候选”形态,最终能否原地复用存储空间,还要看输入列、目标列、路径和新值大小。

一句话结论:把 JSON 列自身作为 JSON_SET() 的输入,并替换已经存在的成员,才是最值得优先尝试的写法;用 JSON_STORAGE_FREE() 观察结果,不能只看 SQL 表面。

官方文档:https://dev.mysql.com/doc/refman/8.4/en/

先看最小 JSON_SET 写法

假设表里有一个名为 profile 的 JSON 列,里面已经存在 nickname。我会先把更新写成“从 profile 读,仍然写回 profile”,而不是先在应用层拼出整段 JSON 再覆盖整列。

-- 只替换已有成员,让 profile 同时充当输入列和目标列
UPDATE user_profile
SET profile = JSON_SET(profile, '$.nickname', '小周')
WHERE user_id = 42;

-- 观察 JSON 列更新后是否留下可复用空间
SELECT
    JSON_STORAGE_SIZE(profile) AS json_size,
    JSON_STORAGE_FREE(profile) AS json_free
FROM user_profile
WHERE user_id = 42;

这条写法满足了最重要的表达式形态:目标是 JSON 列 profile,JSON_SET() 的第一个参数也是这个列。若路径 $.nickname 已经存在,且新值能放进原成员可利用的空间,MySQL 才有机会做原地部分更新。JSON_STORAGE_FREE() 返回大于 0 的值时,说明这次更新后的二进制 JSON 表示中出现了可复用的空闲空间;返回 0 则不能证明发生了部分更新。

UPDATE、JSON_SET、已有路径和 JSON_STORAGE_FREE 之间的静态关系结构图
图1:JSON_SET 部分更新判据的静态说明图,展示同列读写、已有路径与存储观测函数的关系,不是运行截图。

我会先检查三个条件

为了避免把“使用了 JSON_SET”当成结论,我通常按下面三个条件检查。这三个条件是静态判断依据,真正的存储变化仍然要结合观测函数来看。

检查项更有利的形态常见偏离
列类型目标列声明为 JSON把 JSON 当普通字符串列保存
读写关系profile = JSON_SET(profile, ...)profile_a = JSON_SET(profile_b, ...)
路径与值替换已有成员,且新值有机会放入原空间新增成员、改变结构或新值明显变大

尤其是第二项,我以前排查性能时只盯着函数名,忽略了“输入列和目标列必须是同一列”。如果是跨列生成新文档,即便函数仍然叫 JSON_SET(),也不能按同样的部分更新路径理解。

JSON_STORAGE_FREE 才是比较实用的观察点

JSON_STORAGE_SIZE() 适合看 JSON 二进制表示占用的空间,JSON_STORAGE_FREE() 适合看过去的部分更新有没有留下可用空闲空间。两者最好一起查,因为只看业务字段是否变化,无法区分“逻辑值变了”和“底层存储是否复用了空间”。

-- 同时查看当前占用与部分更新留下的空闲空间
SELECT
    user_id,
    JSON_STORAGE_SIZE(profile) AS json_size,
    JSON_STORAGE_FREE(profile) AS json_free,
    profile->>'$.nickname' AS nickname
FROM user_profile
WHERE user_id = 42;

-- 用 JSON_REPLACE 只替换已存在的路径,避免把新增路径误当成更新
UPDATE user_profile
SET profile = JSON_REPLACE(profile, '$.nickname', '小周同学')
WHERE user_id = 42;

这里的 json_free 是存储观察值,不是“本次更新命中了几行”的计数。它可能在连续的缩小更新后累积,也可能因为新值更大或无法利用原空间而保持为 0。不要把它当成绝对的性能承诺,更不要用一次返回 0 就推断整张表都没有部分更新。

为什么同样写 JSON_SET 也可能不是部分更新

下面几种写法很像,但在我实际改 SQL 时需要分开看。第一种是直接给 JSON 列赋完整文档,优化器没有 JSON_SET() 这样的局部变更表达式可分析;第二种把值放进用户变量,用户变量本身不会获得 JSON 列原地部分更新的存储路径;第三种从另一列读再写到目标列,也破坏了同列输入条件。

-- 直接赋值整段文档:这是整值替换的表达式形态
UPDATE user_profile
SET profile = '{"nickname":"小周","level":3}'
WHERE user_id = 42;

-- 用户变量保存 JSON 值:更新变量不会让表中的 JSON 列原地复用空间
SET @profile_doc = '{"nickname":"小周"}';
SET @profile_doc = JSON_SET(@profile_doc, '$.level', 3);

-- 跨列读取再写入:输入列与目标列不同
UPDATE user_profile_archive
SET profile = JSON_SET(profile_snapshot, '$.level', 3)
WHERE user_id = 42;

还有一个容易忽略的边界:把新键加到对象里,或把数组结构扩得更大,通常不属于“替换已有值”的理想场景;新值如果比原位置需要更多空间,也可能无法按原地部分更新处理。此时逻辑结果仍然正确,但存储层可能回到完整文档写入,不能只凭 SQL 形状宣称优化已经生效。

JSON列、同列输入、旧路径、新值大小与整值重写边界结构图
图2:JSON_SET 是否具备原地更新条件的边界说明图,突出同列输入、已有路径和空间条件,不是运行截图。

一段我会留在迁移脚本旁边的完整片段

如果要把一个频繁变化的配置字段从整值覆盖改成局部更新,我会把写法和观测语句放在一起,方便后续接手的人知道“改动什么”和“看什么”。

-- 先确认目标行存在,避免把无匹配更新误判成存储优化失败
SELECT user_id, profile->>'$.theme' AS old_theme
FROM user_profile
WHERE user_id = 42;

-- 只替换已有 theme 成员,输入列与目标列保持一致
UPDATE user_profile
SET profile = JSON_SET(profile, '$.theme', 'dark')
WHERE user_id = 42;

-- 读取逻辑值和存储指标,记录到本次变更的排查笔记中
SELECT
    profile->>'$.theme' AS new_theme,
    JSON_STORAGE_SIZE(profile) AS json_size,
    JSON_STORAGE_FREE(profile) AS json_free
FROM user_profile
WHERE user_id = 42;

这段 SQL 的重点不在于保证每一次都出现正数的 json_free,而在于把判断链条固定下来:目标列是不是 JSON、是否同列读写、路径是不是已有成员、新值是否可能放进原空间,最后再看存储函数返回什么。

最后留一张速查表

现象优先判断下一步
JSON_SET 更新后 json_free 大于 0有部分更新留下空闲空间的迹象记录本次 SQL 形态和数据大小
JSON_SET 更新后 json_free 为 0可能没有空闲空间,或没有走部分更新路径检查路径、同列读写和新值大小
直接给 JSON 列赋完整字符串不属于部分更新表达式改成同列 JSON_SET/JSON_REPLACE 再观察
用户变量中执行 JSON_SET变量值总是完整替换把局部更新放回 JSON 列 UPDATE

所以,MySQL JSON 部分更新真正生效的判断顺序是:先看表达式是否给了优化器可识别的局部修改形态,再看路径和空间条件,最后用 JSON_STORAGE_FREE() 与 JSON_STORAGE_SIZE() 观察存储结果。这样比单独搜索“JSON_SET 性能”更容易定位为什么一条看似正确的 SQL 没有达到预期。

相关问题

  • JSON_SET 和 JSON_REPLACE 的路径不存在时行为有什么区别?
  • 为什么同样更新一个键,新值变长后 json_free 仍然是 0?
  • JSON 部分更新和行格式的二进制日志记录是不是一回事?
  • 什么时候应该把高频字段从 JSON 列拆成普通列?
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>