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 则不能证明发生了部分更新。

我会先检查三个条件
为了避免把“使用了 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 形状宣称优化已经生效。

一段我会留在迁移脚本旁边的完整片段
如果要把一个频繁变化的配置字段从整值覆盖改成局部更新,我会把写法和观测语句放在一起,方便后续接手的人知道“改动什么”和“看什么”。
-- 先确认目标行存在,避免把无匹配更新误判成存储优化失败
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 列拆成普通列?
-
332 收藏
-
374 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
280 收藏
-
数据库 · MySQL | 1小时前 | MySQL · InnoDB · 数据库运维 · mysql Threads 锁等待 data_locks performance_schema data_lock_waits INNODB_TRX 阻塞事务458 收藏
-
132 收藏
-
403 收藏
-
数据库 · MySQL | 1小时前 | MySQL · 事务 · 故障排查 · mysql transaction_read_only INNODB_TRX START TRANSACTION SET TRANSACTION102 收藏
-
124 收藏
-
231 收藏
-
335 收藏
-
488 收藏
-
483 收藏
-
315 收藏
-
166 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习