一篇文章详解你不知道的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数据类型的操作,等待大家的继续探索。

相关推荐
卷Java2 小时前
用户权限控制功能实现说明
java·服务器·开发语言·数据库·servlet·微信小程序·uni-app
数据知道3 小时前
Go基础:json文件处理详解(标准库`encoding/json`)
开发语言·后端·golang·json·go语言
荣光波比4 小时前
MySQL数据库(八)—— MySQL全量+增量备份方案:从脚本开发到连锁餐饮场景落地
运维·数据库·mysql·云计算
qq_340474029 小时前
3.0 labview使用SQLServer
数据库·sqlserver·labview
靡樊10 小时前
MySQL:C语言链接
数据库·mysql
一只游鱼10 小时前
linux使用yum安装数据库
linux·mysql·adb
gopher951112 小时前
go中的Ticker
数据库·golang
熏鱼的小迷弟Liu12 小时前
【MySQL】一篇讲透MySQL的MVCC机制!
数据库·mysql
key0614 小时前
网络安全等级保护测评实施过程
数据库·安全·web安全·安全合规