当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL JSON 部分更新什么时候真的生效

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

来源:17golang原创 2026-10-20 23:02:39 0浏览 收藏

我第一次把大字段拆成 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删除
Redis Streams 怎样用 XREADGROUP 读取消费组消息Redis Streams 怎样用 XREADGROUP 读取消费组消息
上一篇
Redis Streams 怎样用 XREADGROUP 读取消费组消息
Redis XADD NOMKSTREAM 怎样避免自动创建空 Stream
下一篇
Redis XADD NOMKSTREAM 怎样避免自动创建空 Stream
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    570次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    614次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    641次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    601次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    419次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码