Skip to content

S12-02 数据库-MySQL-JSON

[TOC]

概述

MySQL JSON 语法全解析:

MySQL 5.7 开始原生支持 JSON 数据类型,8.0 做了大量增强,提供了结构化存储+原生查询/修改能力,兼顾非结构化数据的灵活度和关系型数据库的查询性能。以下从数据定义值构造查询提取修改更新聚合/条件判断索引优化六个维度,详解 JSON 核心语法,覆盖所有高频操作并附示例。

JSON 数据类型定义

建表时指定 JSON 列

建表时指定 JSON 列:

JSON 是 MySQL 原生数据类型,建表时直接声明,无需指定长度,MySQL 会自动验证 JSON 格式的合法性(非法格式插入会报错)。

sql
-- 基础示例:用户表,扩展字段用 JSON 存储
CREATE TABLE `user` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL,
  `ext_info` JSON COMMENT '用户扩展信息:年龄、爱好、地址',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

允许的 JSON 列属性

允许的 JSON 列属性:

  • 支持 DEFAULT(MySQL 8.0.13+),默认值必须是合法的 JSON 常量(如 '{}''[]');
  • 不支持 AUTO_INCREMENTCHARSET/COLLATE(JSON 内部编码为 utf8mb4);
  • 支持 NOT NULL,空 JSON 需用 '{}'/'[]',而非 NULL
sql
-- MySQL 8.0.13+ 支持 JSON 默认值
CREATE TABLE `goods` (
  `id` INT PRIMARY KEY,
  `attrs` JSON NOT NULL DEFAULT '{}' COMMENT '商品属性,默认空对象'
);

JSON 值构造语法

JSON 值构造语法:

插入/更新 JSON 列时,需构造合法的 JSON 数据,MySQL 提供直接写 JSON 字符串内置函数构造两种方式,推荐函数构造(自动处理引号转义,避免语法错误)。

直接写入 JSON 字符串

直接写入 JSON 字符串:

要求严格的 JSON 格式:键必须用双引号,值支持字符串(双引号)、数字、布尔、null、数组、对象,单引号会报错。

sql
-- 插入 JSON 对象/数组
INSERT INTO `user` (name, ext_info) VALUES
('张三', '{"age": 20, "hobby": ["篮球", "游戏"], "address": {"province": "广东", "city": "深圳"}}'),
('李四', '{"age": 25, "hobby": ["读书"], "address": null}');

内置构造函数

内置构造函数(推荐):

JSON_OBJECT()

JSON_OBJECT([key, val], ...):构造 JSON 对象:

语法:键为字符串(可省略引号),值为任意MySQL类型,自动转换为JSON对应类型,键值对必须成对出现。 示例

sql
INSERT INTO `user` (name, ext_info)
VALUES ('王五', JSON_OBJECT(
  'age', 22,
  'hobby', JSON_ARRAY('跑步', '游泳'), -- 嵌套构造数组
  'address', JSON_OBJECT('province', '浙江', 'city', '杭州')
));

JSON_ARRAY()

JSON_ARRAY([val], ...):构造 JSON 数组:

语法:参数为任意MySQL类型,自动转换为JSON数组元素,无参数则生成空数组。 示例

sql
SELECT JSON_ARRAY(1, '2', true, null); -- 结果:[1, "2", true, null]

JSON_MERGE_PRESERVE()

JSON_MERGE_PRESERVE():合并多个 JSON 数据:

MySQL 8.0 废弃 JSON_MERGE,推荐 JSON_MERGE_PRESERVE(保留所有键值,数组拼接,对象键冲突则保留多个值),还有 JSON_MERGE_PATCH(对象键冲突则覆盖,类似 JSON 补丁)。 语法JSON_MERGE_PRESERVE(json1, json2, ...)示例

sql
SELECT JSON_MERGE_PRESERVE('{"a":1}', '{"b":2, "a":3}', '[4,5]');
-- 结果:{"a": [1, 3], "b": 2, "0": 4, "1": 5}
SELECT JSON_MERGE_PATCH('{"a":1, "b":2}', '{"b":3, "c":4}');
-- 结果:{"a": 1, "b": 3, "c": 4}(覆盖b,新增c)

JSON_QUOTE()

JSON_QUOTE():将普通字符串转义为 JSON 字符串:

用途:解决普通字符串含双引号/反斜杠的转义问题,生成合法的 JSON 字符串值。 示例

sql
SELECT JSON_QUOTE('He said "Hello"'); -- 结果:"He said \"Hello\""
INSERT INTO `user` (name, ext_info)
VALUES ('赵六', JSON_OBJECT('desc', JSON_QUOTE('昵称:"小六"')));

MySQL 类型与 JSON 类型的自动转换

MySQL 类型与 JSON 类型的自动转换:

MySQL 类型JSON 类型示例
INT/BIGINT/DECIMALJSON 数字123 → 123
VARCHAR/TEXTJSON 字符串'test' → "test"
BOOL/TINYINT(1)JSON 布尔true → true,0 → false
NULLJSON nullNULL → null
DATE/DATETIMEJSON 字符串NOW() → "2026-01-22..."

JSON 数据查询与提取语法

JSON 数据查询与提取语法:

核心是从 JSON 对象/数组中提取指定值,MySQL 提供两种语法风格箭头运算符->/->>,简洁)和内置提取函数JSON_EXTRACT,功能全面),箭头运算符是提取函数的语法糖。

基础提取:JSON 对象

基础提取:JSON 对象(按键取值):

->

->:提取值,保留 JSON 原生格式(如字符串带双引号):

->>

->>:提取值,自动转义为 MySQL 普通字符串(无引号,最常用):

JSON_EXTRACT()

JSON_EXTRACT(json_col, path, ...):原生函数,path 为 JSON 路径:

JSON 路径规则

  • $.键 表示对象的顶级键;
  • $.键.子键 表示嵌套对象的子键;
  • 路径中的特殊键(含空格/特殊字符)用 "$.键名" 包裹,如 $."user name"

示例:查询用户的年龄、城市、爱好(基于前文 user 表)

sql
-- 箭头运算符(推荐)
SELECT
  name,
  ext_info->'$.age' AS age, -- 保留JSON格式,数字无引号,字符串有引号
  ext_info->>'$.address.city' AS city, -- 转普通字符串,无引号
  ext_info->'$.hobby' AS hobby -- 提取整个数组
FROM `user`;

-- 等价的 JSON_EXTRACT 函数
SELECT
  name,
  JSON_EXTRACT(ext_info, '$.age') AS age,
  JSON_UNQUOTE(JSON_EXTRACT(ext_info, '$.address.city')) AS city -- JSON_UNQUOTE 去除引号,等价 ->>
FROM `user`;

-- 提取含特殊字符的键(示例:ext_info 含 "user name" 键)
SELECT ext_info->>'$."user name"' FROM `user`;

基础提取:JSON 数组

基础提取:JSON 数组(按下标取值):

JSON 路径规则:数组下标从 0 开始,用 $.数组键[下标] 表示,支持负下标(-1 表示最后一个元素,-2 倒数第二个,MySQL 8.0+)。 示例:提取用户的第一个爱好、最后一个爱好

sql
SELECT
  name,
  ext_info->>'$.hobby[0]' AS first_hobby, -- 第一个爱好
  ext_info->>'$.hobby[-1]' AS last_hobby   -- 最后一个爱好(MySQL 8.0+)
FROM `user`;

批量提取:JSON_TABLE

批量提取:JSON_TABLE(MySQL 8.0+ 核心函数):

将 JSON 数组转换为关系型表,实现「JSON 数据联表查询」「批量统计」,是处理 JSON 数组的最核心函数,必须掌握

语法::

sql
JSON_TABLE(
  json_expr, -- JSON 数组/表达式
  path COLUMNS ( -- 数组元素的路径,* 表示每个元素
    列名1 类型 PATH '元素路径1' [AS JSON], -- [AS JSON] 保留JSON格式
    列名2 类型 PATH '元素路径2' [DEFAULT '默认值'] ON EMPTY, -- 空值默认
    列名3 类型 FOR ORDINALITY -- 生成行号,从1开始
  )
) AS 表别名

示例:将用户爱好数组转为表,统计每个爱好的出现次数:

sql
-- 步骤1:用 JSON_TABLE 拆分爱好数组;步骤2:分组统计
SELECT
  hobby,
  COUNT(*) AS count
FROM `user`,
JSON_TABLE(
  ext_info->'$.hobby',
  '$[*]' COLUMNS (hobby VARCHAR(50) PATH '$') -- $ 表示数组的每个元素
) AS t
GROUP BY hobby
ORDER BY count DESC;

高级示例:嵌套 JSON 数组拆分(如 ext_info 含 [{"course":"数学","score":90},{"course":"语文","score":85}]:

sql
-- 拆分学生成绩数组,查询张三的所有科目和分数
SELECT
  u.name,
  t.course,
  t.score
FROM `user` u,
JSON_TABLE(
  u.ext_info->'$.score',
  '$[*]' COLUMNS (
    course VARCHAR(50) PATH '$.course',
    score INT PATH '$.score'
  ) AS t
WHERE u.name = '张三';

其他提取函数

其他提取函数:

函数用途示例
JSON_KEYS(json)提取 JSON 对象的所有顶级键JSON_KEYS(ext_info) → ["age","hobby"]
JSON_LENGTH(json)统计 JSON 长度(对象:键数;数组:元素数)JSON_LENGTH(ext_info->'$.hobby') → 2
JSON_CONTAINS_PATH(json, one/all, path...)判断是否存在指定路径(one:任意一个存在;all:所有都存在)JSON_CONTAINS_PATH(ext_info, 'one', '$.address', '$.phone') → 1(存在address)

JSON 数据修改与更新语法

JSON 数据修改与更新语法:

MySQL 支持原地修改 JSON 列的指定部分,无需整列更新,提供一系列专门的修改函数,覆盖「新增/修改键值」「数组增删元素」「删除键/元素」等所有场景,均为 MySQL 8.0+ 增强,5.7 仅支持部分

JSON 对象修改

JSON 对象修改(新增/更新/删除键):

JSON_SET()

JSON_SET(json, path, val, ...):新增/更新键值(存在则更新,不存在则新增):

最常用,支持嵌套键,值为任意MySQL类型。

sql
-- 给张三新增phone键,更新age为21,修改地址的city为广州
UPDATE `user`
SET ext_info = JSON_SET(ext_info, '$.phone', '13800138000', '$.age', 21, '$.address.city', '广州')
WHERE name = '张三';

JSON_INSERT()

JSON_INSERT(json, path, val, ...):仅新增键值(存在则忽略,不更新):

sql
-- 给李四新增phone键(若已存在则不修改)
UPDATE `user`
SET ext_info = JSON_INSERT(ext_info, '$.phone', '13900139000')
WHERE name = '李四';

JSON_REPLACE()

JSON_REPLACE(json, path, val, ...):仅更新键值(不存在则忽略,不新增):

sql
-- 仅更新张三的age为22(若age不存在则不操作)
UPDATE `user`
SET ext_info = JSON_REPLACE(ext_info, '$.age', 22)
WHERE name = '张三';

JSON_REMOVE()

JSON_REMOVE(json, path, ...):删除指定路径的键/元素:

sql
-- 删除张三的phone键,删除李四的address键
UPDATE `user` SET ext_info = JSON_REMOVE(ext_info, '$.phone') WHERE name = '张三';
UPDATE `user` SET ext_info = JSON_REMOVE(ext_info, '$.address') WHERE name = '李四';

JSON 数组修改

JSON 数组修改(增删/插入/替换元素):

JSON_ARRAY_APPEND()

JSON_ARRAY_APPEND(json, path, val, ...):向数组末尾追加元素:

sql
-- 给张三的hobby数组追加"健身"
UPDATE `user`
SET ext_info = JSON_ARRAY_APPEND(ext_info, '$.hobby', '健身')
WHERE name = '张三';

JSON_ARRAY_INSERT()

JSON_ARRAY_INSERT(json, path, val, ...):向数组指定下标插入元素:

sql
-- 给张三的hobby数组第1个位置(下标0)插入"书法"
UPDATE `user`
SET ext_info = JSON_ARRAY_INSERT(ext_info, '$.hobby[0]', '书法')
WHERE name = '张三';

JSON_REMOVE()

JSON_REMOVE(json, path):删除数组指定下标元素(复用对象删除函数):

sql
-- 删除张三的hobby数组最后一个元素(MySQL 8.0+ 负下标)
UPDATE `user`
SET ext_info = JSON_REMOVE(ext_info, '$.hobby[-1]')
WHERE name = '张三';

JSON_ARRAY_REMOVE()

JSON_ARRAY_REMOVE():MySQL 8.0.31+ 新增,批量删除数组元素:

sql
-- 删除hobby数组中的"游戏"和"读书"(按值删除)
UPDATE `user`
SET ext_info = JSON_ARRAY_REMOVE(ext_info, '$.hobby', '游戏', '读书');

批量修改:JSON_MERGE_PATCH

批量修改:JSON_MERGE_PATCH(覆盖式合并):

将新的 JSON 补丁覆盖到原 JSON 对象,实现批量更新,键冲突则覆盖,无则新增,适合多键批量修改。

sql
-- 批量更新张三的ext_info:覆盖age,新增email,保留其他键
UPDATE `user`
SET ext_info = JSON_MERGE_PATCH(ext_info, '{"age":23, "email":"zhangsan@test.com"}')
WHERE name = '张三';

JSON 条件判断与聚合语法

条件判断:JSON_CONTAINS

条件判断:JSON_CONTAINS(判断是否包含指定值):

语法JSON_CONTAINS(json, val [, path]),判断 JSON 数据中是否包含指定值,val 必须是合法的 JSON 格式(如字符串需双引号,用 JSON_QUOTE 构造)。 示例:查询爱好包含「篮球」的用户,查询地址省份为「广东」的用户

sql
-- 爱好包含篮球(val为JSON数组,判断数组是否包含该元素)
SELECT * FROM `user` WHERE JSON_CONTAINS(ext_info->'$.hobby', JSON_QUOTE('篮球'));

-- 地址省份为广东(val为JSON字符串,判断对象键值是否匹配)
SELECT * FROM `user` WHERE JSON_CONTAINS(ext_info->'$.address', JSON_QUOTE('广东'), '$.province');

模糊查询:JSON_SEARCH(按值查路径,支持模糊匹配):

语法JSON_SEARCH(json, one/all, search_str [, escape_char [, path...]])

  • one:找到第一个匹配值就返回路径;all:返回所有匹配值的路径(数组);
  • search_str:支持通配符 %(任意字符)、_(单个字符),类似 MySQL LIKE
  • 返回值:JSON 路径字符串,无匹配则返回 NULL

示例:查询爱好包含「球」的用户,查询地址含「深」的用户

sql
-- 爱好包含"球"(通配符%),one表示找到第一个即可
SELECT * FROM `user` WHERE JSON_SEARCH(ext_info->'$.hobby', 'one', '%球%') IS NOT NULL;

-- 地址的任意子键含"深"(path为$.address,表示遍历address的所有子键)
SELECT * FROM `user` WHERE JSON_SEARCH(ext_info, 'one', '%深%', null, '$.address') IS NOT NULL;

JSON 数据聚合计算

JSON 数据聚合计算:

直接对提取的 JSON 数值进行聚合(SUM/AVG/MAX/MIN/COUNT),提取的数值用 -> 保留数字格式,或 ->> 转成 MySQL 数值类型均可,MySQL 会自动隐式转换。 示例:统计用户的平均年龄、最大年龄,统计爱好数量大于1的用户数

sql
-- 平均年龄、最大年龄
SELECT AVG(ext_info->'$.age') AS avg_age, MAX(ext_info->'$.age') AS max_age FROM `user`;

-- 爱好数量大于1的用户数
SELECT COUNT(*) FROM `user` WHERE JSON_LENGTH(ext_info->'$.hobby') > 1;

JSON 索引优化

JSON 索引优化(核心性能点):

原生 JSON 列本身不支持直接建索引,但 MySQL 提供两种索引方案,解决 JSON 数据的查询性能问题,生产环境必须做索引优化,否则大表查询会全表扫描。

虚拟列 + 普通索引

虚拟列 + 普通索引(MySQL 5.7+,最常用):

原理:

原理::

基于 JSON 列的指定路径创建虚拟列(生成列),再对虚拟列建普通索引,查询时通过虚拟列过滤,MySQL 会自动优化执行计划。

步骤:

步骤::

  1. 建表时/建表后创建持久化虚拟列STORED,存储到磁盘,查询更快);
  2. 对虚拟列创建普通 B 树索引;
  3. 查询时直接使用虚拟列(或用 JSON 路径,MySQL 会自动关联虚拟列)。

示例:给用户的 age 键、address.province 键建索引

示例:给用户的 age 键、address.province 键建索引:

sql
-- 步骤1:给已存在的user表添加虚拟列
ALTER TABLE `user`
ADD COLUMN `age` INT STORED AS (ext_info->>'$.age'), -- 基于$.age的虚拟列
ADD COLUMN `province` VARCHAR(50) STORED AS (ext_info->>'$.address.province');

-- 步骤2:对虚拟列建索引
CREATE INDEX idx_user_age ON `user`(age);
CREATE INDEX idx_user_province ON `user`(province);

-- 步骤3:查询(两种方式均可,MySQL 会自动走索引)
SELECT * FROM `user` WHERE age = 20; -- 直接用虚拟列(推荐)
SELECT * FROM `user` WHERE ext_info->>'$.age' = 20; -- 用JSON路径,MySQL自动优化

JSON 多值索引

JSON 多值索引(MySQL 8.0.17+,针对数组):

原理:

原理::

对 JSON 数组创建多值索引,支持数组的 JSON_CONTAINS/JSON_SEARCH 查询走索引,解决数组查询的性能问题,无需创建虚拟列。

语法:CREATE INDEX 索引名 ON 表名((CAST(json_col->'$.数组键' AS JSON ARRAY)));

示例:给用户的 hobby 数组建多值索引

示例:给用户的 hobby 数组建多值索引:

sql
-- 给hobby数组建多值索引
CREATE INDEX idx_user_hobby ON `user`((CAST(ext_info->'$.hobby' AS JSON ARRAY)));

-- 查询爱好包含篮球的用户,自动走索引
SELECT * FROM `user` WHERE JSON_CONTAINS(ext_info->'$.hobby', JSON_QUOTE('篮球'));

索引使用注意事项

索引使用注意事项:

  1. 虚拟列建议用 STORED(持久化),而非 VIRTUAL(计算型,查询时计算,性能差);
  2. 多值索引仅支持 JSON 数组,且仅对 JSON_CONTAINS/JSON_SEARCH 优化;
  3. 避免对高频修改的 JSON 路径建索引,索引会增加更新开销;
  4. EXPLAIN 验证索引是否生效,生效则 typeref/rangekey 显示索引名。

MySQL JSON 版本兼容性说明

MySQL JSON 版本兼容性说明:

功能/函数MySQL 5.7MySQL 8.0+
JSON 数据类型✔️✔️(增强验证)
->/->> 箭头运算符✔️✔️(支持负下标)
JSON_OBJECT/JSON_ARRAY✔️✔️
JSON_TABLE✔️(核心增强)
负下标(hobby[-1])✔️
JSON 列 DEFAULT 值8.0.13+ ✔️
多值索引8.0.17+ ✔️
JSON_MERGE_PATCH/PRESERVE✔️(废弃 JSON_MERGE)
JSON_ARRAY_REMOVE8.0.31+ ✔️

核心最佳实践

核心最佳实践:

  1. 适用场景:存储非结构化/半结构化数据(如用户扩展信息、商品属性)、高频新增字段但不想改表结构的场景;不适用:需要频繁联表/聚合、数据结构固定的场景(优先用普通列)。
  2. 格式规范:插入时优先用 JSON_OBJECT/JSON_ARRAY 构造,避免手动写 JSON 字符串导致的转义错误。
  3. 查询规范:处理 JSON 数组优先用 JSON_TABLE,实现关系型查询;单值提取用 ->> 箭头运算符,简洁高效。
  4. 性能规范:大表必须建虚拟列索引/多值索引,避免全表扫描;控制 JSON 列的大小,不要存储超大 JSON(如超过100KB,建议用 TEXT 并手动解析)。
  5. 版本选择:生产环境优先用 MySQL 8.0+,5.7 缺失大量核心功能(如 JSON_TABLE),性能和灵活性较差。
  6. 避免滥用:不要将所有字段都塞到 JSON 列,核心查询字段(如用户ID、商品价格)仍用普通列,JSON 仅存扩展字段。

以上为 MySQL JSON 所有核心语法,覆盖开发、查询、更新、优化全流程,结合示例可直接在生产环境使用,重点掌握箭头运算符JSON_TABLE虚拟列索引三个核心点,即可解决 99% 的 JSON 开发场景。