一篇文章详解你不知道的MySQL JSON数据类型

MySQL从5.7.8开始支持JSON数据类型,在此之前,针对JSON类型的数据,只能通过字符类型来进行存储和处理。

虽然MySQL对JSON类型已经支持很长时间了,而且具有不少优势,但不少开发人员还对JSON类型的存储抱有一定的偏见。

这篇文章就带大家详细学习一遍JSON数据类型的使用和特点,帮助大家更好地认识和实践JSON类型。

MySQL的JSON数据类型简介

MySQL从5.7版本开始正式支持JSON数据类型,MySQL 8.0又丰富了JSON函数集,加入了更强大的功能例如 JSON_TABLE()(将JSON数据动态映射为关系型表)。

JSON类型的数据以二进制格式 存储,称为binary JSON (BIN_JSON) ,相比明文字符串格式更高效,支持快速访问和解析。这种存储方式允许直接访问嵌套的属性,不需要将整个JSON字符串加载到内存中进行解析。

MySQL提供一系列原生的JSON函数,允许对JSON内容进行查询、修改和操作。例如:

  • JSON_EXTRACT():提取JSON数据中的特定字段。
  • JSON_SET():更新JSON数据中的某些字段。
  • JSON_ARRAY():创建JSON数组。
  • JSON_OBJECT():创建JSON对象。
  • JSON_CONTAINS():检查JSON数据中是否包含某个键或值。
  • JSON_ARRAYAGG()JSON_OBJECTAGG():可以在GROUP BY查询中生成JSON结果。

对于插入JSON类型字段的数据,MySQL会自动验证插入到JSON列的数据是否是合法的JSON格式。如果格式无效,会报错。

另外,JSON类型还支持创建虚拟生成列(Generated Column) 来基于JSON字段的某些值建立索引。此部分实现可参考《如何为MySQL中的JSON字段设置索引》一文。

使用JSON类型前后对比

在进行JSON类型介绍之前,我们先来看一下不使用JSON数据类型和使用JSON类型的差别。

未使用JSON类型

在 MySQL 5.7 版本之前(或者不使用 JSON 类型的情况下),我们通常会将 JSON 格式的数据以字符串的形式存储在传统的 CHARVARCHARTEXT 类型的列中。此时,数据库并不具备对 JSON 数据的原生操作能力。

此时,如果需要读取 JSON 的某个字段或元素,应用程序通常会从数据库中查询 JSON 字符串,然后在应用层通过 JSON 解析库(如 Python 的 json 模块)解析成 JSON 对象,最终提取所需的字段。

其中,解析和获取指定元素的过程,用Python实现的示例代码如下:

makefile 复制代码
import json
​
# 从数据库中读取到的 JSON 字符串(模拟)
jsonStr = '{"name":"John","age":30,"city":"New York"}'
​
# 将 JSON 字符串解析为 Python 的字典对象
y = json.loads(jsonStr)
​
# 读取 JSON 对象中指定的元素值
print(y['age'])  # 输出:30

这里的 json.loads() 方法可将 JSON 字符串解析为 Python 的原生字典类型,之后可以通过键值访问方式提取某个字段。

这种方式虽然能够达到存储和读取JSON数据类型的目的,但却存在着一些缺点:

  • 第一,消耗磁盘IO:从数据库中查询存储的大型 JSON 格式字符串会触发磁盘 IO 操作,而且存储类型(如 TEXTVARCHAR)可能会影响 IO 性能。如果 JSON 字符串较大或查询结果返回大量数据,磁盘 IO 会成为瓶颈。
  • 第二,如果JSON数据比较大,对网络带宽造成压力。尤其是在大型分布式系统中,在高并发的场景下,会对计算节点的网络链路和吞吐量产生影响。

使用JSON类型

MySQL 从 5.7 版本开始支持 JSON 类型,提供了原生处理 JSON 字段的能力,可以避免需通过应用层解析 JSON 字符串的方式。也就是说,可以直接通过SQL语句即可完成指定JSON元素的查询。

创建表及插入数据,SQL语句示例如下:

sql 复制代码
-- 使用 JSON 类型创建表
CREATE TABLE example (
    id INT AUTO_INCREMENT PRIMARY KEY,
    data JSON
);
​
-- 插入 JSON 数据
INSERT INTO example (data) VALUES ('{"name": "John", "age": 30, "city": "New York"}');
​
-- 查询 JSON 数据中的某个字段
SELECT JSON_EXTRACT(data, '$.age') AS age FROM example;

通过->的方式查询操作:

sql 复制代码
mysql> select data->"$.age" from example;
+---------------+
| data->"$.age" |
+---------------+
| 30            |
+---------------+
1 row in set (0.01 sec)

通过基于JSON_EXTRACT()函数进行查询:

sql 复制代码
mysql> SELECT JSON_EXTRACT(data, '$.age') AS age FROM example;
+------+
| age  |
+------+
| 30   |
+------+
1 row in set (0.00 sec)

可以看出,无论通过上述的哪种方式来进行查询,都只需要从 JSON 中提取查询所需字段,而不是加载整个字符串到内存解析。这不仅节省了网络带宽,还能降低磁盘IO的消耗。

下面,我们就来详细的了解一下MySQL对JSON数据类型的功能支持。

JSON类型的增删改查

下面我们看看 JSON 字段常见的增删改查操作,具体示例依旧采用上面的example表。

插入操作

MySQL 的 JSON 类型遵循 JSON 标准 (RFC 7159),这一标准明确规定了 JSON 数据的有效结构包括以下两种形式:

  • JSON对象{"key": value} 格式的键值对结构)。
  • JSON数组[value1, value2, value3] 格式的值列表结构)。

因此,MySQL支持上述两种结构的数据插入到JSON字段中。

插入JSON对象的SQL语句:

erlang 复制代码
-- 插入 JSON 数据
INSERT INTO example (data) VALUES ('{"name": "John", "age": 30, "city": "New York"}');

插入JSON数组的SQL语句:

sql 复制代码
INSERT INTO example (data) VALUES ('["John", 30, "New York"]');

下面,我们再看一下,如果插入的是一个非JSON的文本,看看效果:

sql 复制代码
mysql> INSERT INTO example (data) VALUES ('师兄奇谈');
ERROR 3140 (22032): Invalid JSON text: "Invalid value." at position 0 in value for column 'example.data'.

这里可以看到,MySQL数据对插入JSON字段类型的数据进行了校验,当插入非JSON类型时,会直接保持。

另外需要注意的是,JSON数据中的KEY不能重复。如果插入的值中存在重复KEY,在 MySQL 8.0.3 之前,遵循 first duplicate key wins 原则,会保留第一个 KEY,后面的将被丢弃掉。从 MySQL 8.0.3 开始,遵循的是 last duplicate key wins 原则,只会保留最后一个 KEY。

构造JSON数组

通常,可通过JSON_ARRAY()来构造JSON数组,示例如下:

sql 复制代码
mysql> select json_array(1, "John", 30, "New York");
+---------------------------------------+
| json_array(1, "John", 30, "New York") |
+---------------------------------------+
| [1, "John", 30, "New York"]           |
+---------------------------------------+

构造JSON对象

通常,可通过JSON_OBJECT()来构造JSON对象,示例如下:

sql 复制代码
mysql> select json_object('name', 'John','age','30','city','New York');
+----------------------------------------------------------+
| json_object('name', 'John','age','30','city','New York') |
+----------------------------------------------------------+
| {"age": "30", "city": "New York", "name": "John"}        |
+----------------------------------------------------------+

查询操作

MySQL 对JSON类型字段提供许多内置函数,可以灵活的处理JSON数据。常见操作包括:提取 JSON 字段值(通过键或路径查询)、检索嵌套字段值、过滤和排序 JSON 数据等。

提取 JSON 字段值

JSON_EXTRACT() 是提取字段值的核心函数,用来从 JSON 数据字段中获取指定路径的值。JSON_EXTRACT() 函数的语法格式如下:

lua 复制代码
JSON_EXTRACT(json_doc, path[, path] ...)

其中,json_doc是 JSON 文档,path 是路径。该函数会从 JSON 文档提取指定路径(path)的元素。如果指定 path 不存在,会返回 NULL。可指定多个 path,匹配到的多个值会以数组形式返回。

获取JSON对象的SQL示例:

sql 复制代码
-- 提取 JSON 字段中的 "name"
SELECT JSON_EXTRACT(data,'$.name') AS name FROM example;
-- 返回结果: "John"
​
-- 提取JSON字段的"name"和"age"字段,结果以数组形式返回
mysql> SELECT JSON_EXTRACT(data,'$.name','$.age')  FROM example;
+-------------------------------------+
| JSON_EXTRACT(data,'$.name','$.age') |
+-------------------------------------+
| ["John", 30]                        |

数组的路径是通过下标来表示的。第一个元素的下标是 0。

获取JSON数组的SQL示例:

sql 复制代码
SELECT JSON_EXTRACT(data,'$[0]') AS name FROM example;
-- 返回结果: "John"

除此之外,数组类型的JSON,还可以通过[M to N] 获取数组的子集,通过[*] 获取数组中的所有元素。

sql 复制代码
mysql> SELECT JSON_EXTRACT(data,'$[0 to 1]') AS name FROM example;
+---------------------------------------------------+
| name                                              |
+---------------------------------------------------+
| ["John", 30]                                      |
+---------------------------------------------------+
​
mysql> SELECT JSON_EXTRACT(data,'$[*]') AS name FROM example;
+--------------------------+
| name                     |
+--------------------------+
| ["John", 30, "New York"] |
+--------------------------+

需要注意的是,虽然JSON类型同时支持JSON对象和JSON数组两种类型,但在使用的时候最好不要两者混用,否者在上述两种查询种会出现一些不符合预期的结果。

比如:

sql 复制代码
mysql> SELECT JSON_EXTRACT(data,'$[0]') AS name FROM example;
+-------------------------------------------------+
| name                                            |
+-------------------------------------------------+
| {"age": 30, "city": "New York", "name": "John"} |
| "John"                                          |
+-------------------------------------------------+

我们预期的是输出"John",但也将JSON对象类型的数据查询了出来。

去除引号以获得原始值

JSON_UNQUOTE() 用于移除 JSON_EXTRACT() 返回值中的双引号,提取字段的原始值。

sql 复制代码
-- 提取字段值并去除引号
SELECT JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) AS name FROM example;
-- 返回结果: John

检索嵌套字段值

如果 JSON 数据包含嵌套结构,可以通过路径表达式逐层访问。假设以下 JSON 数据:

json 复制代码
{
  "name": "John",
  "details": {
    "age": 30,
    "location": {
      "city": "New York",
      "country": "USA"
    }
  }
}

可以查询嵌套字段值:

sql 复制代码
-- 查询嵌套字段 "details.age"
SELECT JSON_EXTRACT(data, '$.details.age') AS age FROM example;
​
-- 查询嵌套字段 "details.location.city"
SELECT JSON_EXTRACT(data, '$.details.location.city') AS city FROM example;

过滤和排序 JSON 数据

通过 WHERE 子句和路径表达式筛选满足条件的数据。

javascript 复制代码
-- 查询 JSON 数据中 "age" 大于 25 的记录
SELECT * FROM example WHERE JSON_EXTRACT(data, '$.age') > 25;
​
-- 查询 JSON 数据中 "name" 为 "John" 的记录
SELECT * FROM example WHERE JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) = 'John';

查询函数的语法糖

在 MySQL 中,提供了基于->->> 操作符的语法糖操作,它们会在底层转换为调用 JSON_EXTRACT() 函数(对于 ->)或 JSON_UNQUOTE(JSON_EXTRACT(...)) 函数(对于 ->>)。

其中,column->path用于提取 JSON 数据字段,等价于 JSON_EXTRACT(column, path),返回结果是 JSON 格式(即带有引号的字符串或其他合法的 JSON 数据类型)。

示例如下:

sql 复制代码
SELECT data->'$.name' AS name FROM example; -- 返回结果: "John"

column->>path用于提取 JSON 字段并返回原始值(移除了 JSON 格式中的双引号)。等价于 JSON_UNQUOTE(JSON_EXTRACT(column, path))

sql 复制代码
-- 使用 `->>` 提取原始值
SELECT data->>'$.name' AS name_raw FROM example;
-- 返回结果: John (原始值,去掉了引号)

MySQL 的 JSON 路径遵循以下规则:

  • 使用 $ 表示 JSON 数据的根节点。
  • 使用 . 表示访问子节点,例如 $.name
  • 使用 [index] 表示访问数组的元素,例如 $[0]
  • 无法通过 ->->> 操作符同时提取多个路径。

如果需要处理复杂路径(例如多层嵌套或多个字段的提取),通常需要结合多个查询或者写入虚拟生成列。

修改操作

修改操作包含JSON_SET()和JSON_REPLACE()两个函数。

JSON_SET()函数

JSON_SET()的语法格式为JSON_SET(json_doc, path, val[, path, val] ...),当执行JSON_SET()函数时:

  • 如果指定路径已经存在,则会更新此路径上的值;
  • 如果指定路径不存在,则会插入新的路径和值。

示例SQL:

sql 复制代码
mysql> SELECT JSON_SET(data, '$.age', 35) AS new_data FROM example;
+-------------------------------------------------+
| new_data                                        |
+-------------------------------------------------+
| {"age": 35, "city": "New York", "name": "John"} |
​
-- 不存在则新增字段
mysql> SELECT JSON_SET(data, '$.gender', 'male') AS new_data FROM example;
+-------------------------------------------------------------------+
| new_data                                                          |
+-------------------------------------------------------------------+
| {"age": 35, "city": "New York", "name": "John", "gender": "male"} |

JSON_REPLACE()函数

JSON_REPLACE()函数的语法格式为JSON_REPLACE(json_doc, path, val[, path, val] ...),主要用于在 JSON 数据中替换指定路径上的值。如果目标路径中已经存在值,那么会将其替换为新的值;如果目标路径不存在,则不会修改 JSON 数据,也不会新增路径。

示例SQL:

sql 复制代码
mysql> SELECT JSON_REPLACE(data, '$.age', 35) AS new_data FROM example;
+-------------------------------------------------+
| new_data                                        |
+-------------------------------------------------+
| {"age": 35, "city": "New York", "name": "John"} |
+-------------------------------------------------+

删除操作

JSON_REMOVE() 是 MySQL 中用于从 JSON 数据中删除指定路径上的值的函数。它适合用于删除 JSON 数据中不需要的字段,同时删除嵌套的字段或数组元素。

  • 如果路径存在,则删除目标路径及其内容。
  • 如果路径不存在,则返回原 JSON 数据,不报错。

语法:

lua 复制代码
JSON_REMOVE(json_doc, path [, path] ...)

参数说明:

  • json_doc: JSON 数据的字段值,可以是 JSON 类型字段或合法的 JSON 字符串。
  • path: 要删除值的路径,遵循 JSON 路径表达式(例如 $ 表示根节点,$.key 表示子节点,$[index] 表示数组索引位置)。

SQL示例:

sql 复制代码
mysql> SELECT JSON_REMOVE(data, '$.gender') AS new_data FROM example;
+-------------------------------------------------+
| new_data                                        |
+-------------------------------------------------+
| {"age": 35, "city": "New York", "name": "John"} |

上述SQL语句删除了JSON 数据中的 gender 字段。

JSON_REMOVE() 同时支持删除多个路径上的字段值。例如,删除 agecity

sql 复制代码
SELECT JSON_REMOVE(data, '$.age', '$.city') AS new_data FROM example;

JSON_REMOVE() 同样支持删除数组中的指定元素,使用索引来定位目标值(索引从 0 开始)。

小结

本文带大家了解了MySQL支持的JSON数据类型以及常见的增删改查操作,相较于之前通过字符串类型来的处理方式,无论从性能和便捷性等方面来说,都有了极大的提升。文中只列举了场景的一些函数和功能,更多关于JSON数据类型的操作,等待大家的继续探索。

相关推荐
鹿角片ljp12 分钟前
动态SQL实现模糊查询
数据库·sql·oracle
晓风残月淡12 分钟前
mysql备份恢复工具Percona XtraBackup使用教程
数据库·mysql
DomDanrtsey15 分钟前
oracle所有表中文与字段最大长度检测
数据库·oracle
Z...........19 分钟前
数据库表设计
数据库
tudficdew23 分钟前
使用Python操作文件和目录(os, pathlib, shutil)
jvm·数据库·python
浒畔居25 分钟前
工具、测试与部署
jvm·数据库·python
云和数据.ChenGuang26 分钟前
python对接mysql和模型类的故障
数据库·python·mysql·oracle·conda·virtualenv
2301_8223827628 分钟前
开发一个简单的Python计算器
jvm·数据库·python
2501_9209992731 分钟前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
_F_y36 分钟前
MySQL用户管理
android·mysql·adb