
一、概念篇:MySQL中的JSON
在快速迭代的互联网时代,数据已成为驱动业务增长和创新的核心要素。然而,随着应用场景的多样化和复杂化,传统固定表结构常常成为开发者的噩梦。正是在这样的背景下,MySQL 作为世界上最流行的开源关系型数据库之一,从 5.7 版本开始正式引入了原生 JSON 数据类型,标志着关系型数据库在处理非结构化数据方面迈出了重要一步,为关系型数据库中存储、查询和操作这类数据提供了强有力的工具。
从 MySQL 5.7 版本引入了JSON数据类型开始,JSON 格式字符串将不再以字符串的形式存储,而是采用一种允许快速读取文本元素的内部二进制格式。本质上,MySQL 的 JSON 数据类型扮演了"桥梁"角色:既保留了关系型数据库的强大事务处理能力和成熟生态系统,又吸收了非结构化数据处理的高度灵活性。在 MySQL 中使用 JSON 数据类型,可以简化半结构化或层级数据的处理流程,带来诸多优势:
- 灵活性:JSON 结构支持动态、层级数据的灵活存储。
- 内置函数:MySQL 提供高效的 JSON 数据查询、更新和验证函数。
- 集成性:可以将关系型数据与 JSON 对象结合,实现混合数据模型。
| 特性 | MySQL 5.7 | MySQL 8.0 |
|---|---|---|
| JSON数据类型 | ✅ 基础支持 | ✅ 完整支持 |
| JSON函数 | ✅ 基础函数 | ✅ 丰富函数库 |
| JSON索引 | ✅ 函数索引 | ✅ 多列索引 |
| JSON性能 | ⚠️ 一般 | ✅ 大幅提升 |
| JSON验证 | ✅ 基础验证 | ✅ 严格验证 |
二、MySQL JSON数据类型基础
2.1 创建 JSON 数据
在MySQL中声明JSON列非常简单,只需在创建表或修改表结构时指定列的数据类型为JSON即可。以下是一个基本的建表示例:
sql
CREATE TABLE users (
id INT PRIMARY KEY auto_increment,
user_id INT NOT NULL UNIQUE,
NAME VARCHAR ( 32 ),
details JSON NOT NULL COMMENT '用户详情信息',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
在上述示例中,details 列被定义为JSON类型,用于存储用户的配置信息或其他非结构化数据。需要注意的是,JSON 列不能有默认值,且不支持直接索引。
2.2 插入 JSON 数据
MySQL 的 JSON 类型遵循 JSON 标准,这一标准明确规定了 JSON 数据的有效结构形式。向 JSON 列插入数据时,必须确保数据是有效的 JSON 格式,在插入时MySQL会自动进行验证,未通过验证的文本将产生一个错误信息,从而阻止数据的正常插入。以下是几种插入JSON数据的常见方式:
-
直接插入JSON字符串
sqlINSERT INTO users (user_id,NAME,details) VALUES (1001,'张三','{ "age": 30, "sex": 1, "email": "zhangsan@example.com", "hobbies": ["coding", "reading"], "address": { "city": "北京", "zip": "10001" }, "role": [ { "roleCode": "guest", "roleName": "访客人员" }, { "roleCode": "tester", "roleName": "测试人员" } ] } '); INSERT INTO users (user_id,NAME,details) VALUES (1002,'李四','{ "age": 25, "sex": 2, "email": "lisi@example.com", "hobbies": ["coding", "reading"], "address": { "city": "上海", "zip": "90001" }, "role": [ { "roleCode": "system", "roleName": "系统管理员" }, { "roleCode": "developer", "roleName": "系统开发人员" } ] } '); -
使用JSON函数生成JSON数据 :MySQL提供如下 JSON 函数来动态构建 JSON 数据,这种方式可以避免手动拼接JSON字符串时可能出现的格式错误。
函数 简要说明 JSON_OBJECT() 将键值对转换为 JSON 对象,键值对自动包装 JSON_ARRAY() 将一组值转换为 JSON 数组,元素自动转JSON类型 sql-- 构造一个 JSON 对象 SELECT JSON_OBJECT('name', 'John Doe', 'age', 30) AS person; -- 构造一个 JSON 数组 SELECT JSON_ARRAY('apple', 'banana', 'cherry') AS fruits;⚠️ JSON_OBJECT 的键名如果是数字,会被强制转字符串(如
JSON_OBJECT(1, 'test')生成{"1": "test"})
2.3 查询 JSON 数据
MySQL 对JSON类型字段提供许多内置函数,可以灵活的处理JSON数据。常见操作包括:提取 JSON 字段值(通过键或路径查询)、检索嵌套字段值、过滤和排序 JSON 数据等。
提取 JSON 字段值
JSON_EXTRACT() 是提取字段值的核心函数,用来从 JSON 数据字段中获取指定路径的值。JSON_EXTRACT() 函数的语法格式如下:
sql
JSON_EXTRACT(json_doc, path[, path] ...)
其中,json_doc 是 JSON 文档,path 是路径。该函数会从 JSON 文档提取指定路径(path)的元素。如果指定 path 不存在,会返回 NULL。可指定多个 path,匹配到的多个值会以数组形式返回。
sql
-- 提取 JSON 字段中的 "email"
SELECT JSON_EXTRACT(details, '$.email') AS email FROM users;
-- 提取JSON字段的"email"和"age"字段,结果以数组形式返回
SELECT JSON_EXTRACT(details,'$.email','$.age') FROM users;
-- 获取JSON数组的SQL
SELECT JSON_EXTRACT(details,'$.hobbies[0]') AS name FROM users;
如果 JSON 数据包含嵌套结构,可以通过路径表达式逐层访问。
sql
-- 查询嵌套字段 "details.location.city"
SELECT JSON_EXTRACT(details, '$.address.city') AS city FROM users;
过滤和排序 JSON 数据
通过 WHERE 子句和路径表达式筛选满足条件的数据。
sql
-- 查询 JSON 数据中 "age" 大于 25 的记录
SELECT * FROM users WHERE JSON_EXTRACT(details, '$.age') > 25;
-- 查询 JSON 数据中 "email" 为 "zhangsan@example.com" 的记录
SELECT * FROM users WHERE JSON_UNQUOTE(JSON_EXTRACT(details, '$.email')) = 'zhangsan@example.com';
⚠️ JSON_UNQUOTE() 用于移除 JSON_EXTRACT() 返回值中的双引号,提取字段的原始值。
2.4 修改 JSON 数据
sql
-- 更新张三的城市为shandong
UPDATE users SET details = JSON_SET(details, '$.address.city', '山东') WHERE name = '张三';
-- 删除张三的地址信息
UPDATE users SET details = JSON_REMOVE(details, '$.address') WHERE name = '张三';
三、JSON数据操作:查询与更新技巧
MySQL 的 JSON 函数库堪称数据库界的"瑞士军刀",可以快速对JSON类型的数据进行查询、更新,这也是 MySQ L中处理 JSON 数据最频繁的操作。这些函数不仅简化了操作流程,还显著提升了数据处理的灵活性和性能。
3.1 查询解析
这是日常开发中最常用的功能,数据被转换为 JSON 格式后,即可对其进行快速解析,高效提取数据。
| 函数 | 简要说明 |
|---|---|
| json_extract(json_doc, path) | 标准提取函数,用于从 JSON 文档中提取指定路径的值 |
| JSON_UNQUOTE | 提取字段值并去除引号 |
sql
-- 原始数据
SET @user = '{
"age": 30,
"sex": 1,
"email": "zhangsan@example.com",
"hobbies": ["coding", "reading"],
"address": {
"city": "北京",
"zip": "10001"
},
"role": [
{
"roleCode": "guest",
"roleName": "访客人员"
},
{
"roleCode": "tester",
"roleName": "测试人员"
}
]
}';
-- 从JSON对象中取出单个值(带引号)
SELECT JSON_EXTRACT(@user, '$.address.city');
SELECT JSON_EXTRACT(@user, '$.email');
-- 从JSON数组取出单个值
SELECT JSON_EXTRACT(@user, '$.hobbies[0]');
SELECT JSON_EXTRACT(@user, '$.role[0]');
除了标准的提取函数,MySQL 提供了基于操作符的语法糖操作:
| 函数 | 简要说明 |
|---|---|
| -> | 用于提取带有引号的字符串或其他合法的 JSON 数据类型 |
| ->> | 用于提取 JSON 字段并返回原始值(移除了 JSON 格式中的双引号) |
sql
-- 提取名字(带双引号)
SELECT name, details->'$.email' FROM users;
-- 提取名字(纯文本,推荐)
SELECT name, details->>'$.email' FROM users;
⚠️ 注意:路径表达式严格区分大小写(如
$.Name和$.name是不同的);如果数组越界或路径不存在,会返回NULL。
3.2 修改与更新
以下函数用于更新或添加 JSON 数据元素:
| 函数 | 简要说明 |
|---|---|
| json_insert(json_doc, path, val[, path, val] ...) | 仅新增 ,只在路径不存在时插入值,若路径已存在则不做任何操作 |
| json_set(json_doc, path, val, ...) | 用于插入或更新JSON文档中的值。如果指定路径不存在则新增;如果存在则覆盖修改 |
| json_replace(json_doc, path, val[, path, val] ...) | 仅修改 。只在路径已存在时替换值,若路径不存在则不做任何操作 |
| json_remove(json_doc, path[, path] ...) | 用于从JSON文档中删除指定路径的数据。 如果路径存在则删除,如果路径不存在,则返回原 JSON 数据 |
sql
-- 原始数据
SET @user = '{
"id": 14,
"name": "张三",
"age": 30
}';
-- 仅在 email 不存在时添加
SELECT json_insert(@user, '$.email', 'test@example.com');
-- 存在则更新字段
SELECT JSON_SET(@user, '$.age', 35);
-- 不存在则新增字段
SELECT JSON_SET(@user, '$.gender', 'male');
-- 如果目标路径中已经存在值,那么会将其替换为新的值
SELECT json_replace(@user, '$.age', 35);
SELECT json_replace(@user, '$.gender', 'male');
-- 对于JSON对象,只需指定要删除的键即可
SELECT json_remove(@user, '$.age');
⚠️ 修改函数会生成新 JSON 对象(类似 Java 的 String 拼接),频繁操作可能触发内存告警。
3.3 类型检查与验证
| 函数 | 简要说明 |
|---|---|
| JSON_TYPE | 获取 JSON 值的数据类型。 除了是MySQL中的数据类型外,还有OBJECT、ARRAY,其中OBJECT表示JSON对象,ARRAY表示JSON数组 |
| JSON_VALID | 判断一个字符串是否是合法的 JSON 格式,常用于数据清洗 如果是,则返回1,否则返回0,如果value的值为NULL,则返回NULL |
sql
-- 原始数据
SET @user = '{
"id": 14,
"name": "张三",
"age": 30,
"address": {"city": "北京", "zip": "100000"},
"hobbies": ["coding", "篮球"]
}';
SELECT JSON_TYPE(@user);
SELECT JSON_TYPE(JSON_EXTRACT(@user,'$.age')); -- INTEGER
SELECT JSON_TYPE(JSON_EXTRACT(@user,'$.name')); -- STRING
SELECT JSON_TYPE(JSON_EXTRACT(@user,'$.hobbies')); -- ARRAY
SELECT JSON_VALID('{"name": "test"}');
SELECT JSON_VALID('{name: test}');
SELECT JSON_VALID('null'), JSON_VALID('Null'), JSON_VALID('NULL');
3.4 搜索与判断
用于在复杂的 JSON 结构中查找数据或判断包含关系:
| 函数 | 简要说明 |
|---|---|
| json_contains(json_doc, target[, path]) | 用于检查JSON文档中是否包含某个值,如果包含则返回1,否则返回0 |
| json_contains_path(json_doc, one_or_all, path [,path ...]) | 判断 JSON 文档是否包含指定的路径 |
| json_search(json_doc, one_or_all, search_str, path) | 查找指定字符串在 JSON 中的路径。 one 表示找到第一个就返回,all 表示返回所有匹配的路径。 |
sql
-- 判断 hobbies 中是否包含 "coding"(注意值需要用 JSON 字符串格式 '"coding"')
SELECT json_contains('{"name":"李四","hobbies":["coding","reading"]}', '"coding"', '$.hobbies');
-- 搜索值为 "张三" 的路径(返回第一个匹配)
SELECT json_search('{"name": "张三", "age": 25, "city": "北京"}', 'one', '张三');
-- 搜索包含 "海" 的值(使用通配符)
SELECT json_search('[{"name":"张三","age":25,"city":"北京"},{"name":"李四","age":30,"city":"上海"}]', 'one', '%海%')
-- 搜索所有包含 "张" 的值
SELECT json_search('[{"name":"张三","age":25,"city":"北京"},{"name":"李四","age":30,"city":"上海"}]', 'all', '%张%')
-- 指定搜索路径:只在 name 字段中搜索
SELECT json_search('{"name": "张三", "age": 25, "city": "北京"}', 'one', '%三%', NULL, '$.name')
⚠️ JSON_SEARCH 只匹配字符串类型,不匹配数字、布尔值等。
四、提升JSON数据处理效率
4.1 数据合并
| 函数 | 简要说明 |
|---|---|
| JSON_MERGE | 用于合并两个JSON文档。在 MySQL 8.0 中,已不建议使用 |
| JSON_MERGE_PRESERVE | 合并 JSON 值,保留重复键的值 |
| JSON_MERGE_PATCH | 合并 JSON 值,丢弃除最后一个值以外的所有值 |
json_merge_preserve() 函数
json_merge_preserve() 在合并 JSON 数组时,会把所有元素简单的拼接成一个大数组:
sql
-- 输出结果:[1, 2, true, false, "a", "b"]
SELECT json_merge_preserve('[1, 2]', '[true, false]','["a","b"]');
-- 输出结果:[1, 2, {"key1": "value1"}]
SELECT json_merge_preserve('[1, 2]', '{"key1": "value1"}');
如果合并的是 JSON 对象,json_merge_preserve()函数会保留所有非重复键值对,对于重复的键,会将其值合并为JSON数组保留下来
sql
-- 输出结果:{"key1": "value1", "key2": "value2"}
SELECT json_merge_preserve('{"key1": "value1"}','{"key2": "value2"}');
-- 输出结果:{"key1": ["value1", "value3"], "key2": "value2"}
SELECT json_merge_preserve('{"key1": "value1"}','{"key2": "value2","key1":"value3"}') duplicate_key1;
-- 输出结果:{"key1": ["value1", "value3", null], "key2": "value2"}
SELECT json_merge_preserve('{"key1": "value1"}','{"key2": "value2","key1":"value3"}','{"key1": null}') last_value_is_null;
json_merge_patch() 函数
json_merge_patch() 在合并JSON数组时,会将每个参数视为独立的元素,并应用"最后的值胜出"原则,只选择最后一个元素,因此只要函数中包含JSON数组,只有最后一个值会保留下来:
sql
-- 输出结果:["a", "b"]
SELECT json_merge_patch('[1, 2]', '[true, false]','["a","b"]');
-- 输出结果:{"key1": "value1"}
SELECT json_merge_patch('[1, 2]', '{"key1": "value1"}');
-- 输出结果:[1, 2]
SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2"}','[1, 2]');
如果合并的都是JSON对象, json_merge_patch()函数会保留所有非重复键值对,对于重复的键,只保留最后出现的值(但最后出现的值不能为null,否则会删除该键值):
sql
-- 输出结果:{"key1": "value1", "key2": "value2"}
SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2"}');
-- 输出结果:{"key1": "value3", "key2": "value2"}
SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2","key1":"value3"}') duplicate_key;
-- 输出结果:{"key2": "value2"}
SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2","key1":"value3"}','{"key1": null}') last_value_is_null;
4.2 JSON 数据聚合
MySQL 提供多种函数用于将查询结果聚合为 JSON 结构:
| 函数 | 简要说明 |
|---|---|
| JSON_ARRAYAGG() | |
| JSON_OBJECTAGG() |
sql
SELECT JSON_ARRAYAGG(name) AS names
FROM ( SELECT JSON_EXTRACT(details, '$.name') AS name FROM users) AS subquery;
4.3 JSON 数组中的范围
数组类型的JSON,可以使用带有 to 关键字的 range 来指定 JSON 数组的子集。比如可以通过 [M to N] 获取数组的子集,通过 [*] 获取数组中的所有元素。
sql
-- 获取数组的第二、第三和第四个元素
SELECT JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[1 to 3]');
SELECT JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[*]');
⚠️ 注意的是,虽然 JSON 类型同时支持 JSON 对象和 JSON 数组两种类型,但在使用的时候最好不要两者混用,否者在上述两种查询种会出现一些不符合预期的结果。
4.4 最右边的数组元素
支持用 last 关键字作为数组中最后一个元素下标的同义词。last-N 形式的表达式可用于相对地址和范围定义,如下所示:
sql
SELECT JSON_EXTRACT('[1, 2, 3, 4, 5]', '$[last-3 to last-1]');
4.5 JSON数据与索引
在 MySQL 的表中,JSON 类型的列通常无法直接建立索引,但可以用虚拟生成列,并根据该列来建立间接索引。但是在 MySQL 8 版本开始,对于 JSON 数组(JSON对象不行),可以建立多值索引。
索引优化方案
对于JSON数据类型需要建立索引,可以对将经常查询的元素提取出来,作为一个虚拟的生成列,并在该列上建立索引,查询时通过虚拟列上索引即可快速定位数据。例如,为用户表中的邮箱创建虚拟列和索引。
sql
ALTER TABLE users ADD COLUMN email VARCHAR(255) GENERATED ALWAYS AS (details->>'$.email') STORED;
CREATE INDEX idx_email ON users (email);
使用全文索引
对于包含大量文本的JSON字段,可以使用全文索引来提高查询性能。
sql
ALTER TABLE users ADD FULLTEXT(details);
SELECT * FROM users WHERE MATCH(details) AGAINST('shanghai');
六、总结
通过本文的介绍,我们了解了MySQL中JSON数据类型的基本操作、常用JSON函数、以及如何通过索引和优化来提高查询性能。JSON数据类型为存储和操作结构化数据提供了灵活性和便利性,在现代数据库应用中具有广泛的应用前景。
