JSON 数据类型是 MySQL 5.7.8 开始支持的。在此之前,只能通过字符类型(CHAR,VARCHAR 或 TEXT )来保存 JSON 文档。
相对字符类型,原生的 JSON 类型具有以下优势:
- 在插入时能自动校验文档是否满足 JSON 格式的要求。
- 优化了存储格式。无需读取整个文档就能快速访问某个元素的值。
在 JSON 类型引入之前,如果我们想要获取 JSON 文档中的某个元素,必须首先读取整个 JSON 文档,然后在客户端将其转换为 JSON 对象,最后再通过对象获取指定元素的值。
下面是 Python 中的获取方式。
import json # JSON 字符串: x = '{ "name":"John", "age":30, "city":"New York"}' # 将 JSON 字符串转换为 JSON 对象: y = json.loads(x) # 读取 JSON 对象中指定元素的值: print(y["age"])
这种方式有两个弊端:一、消耗磁盘 IO,二、消耗网络带宽,如果 JSON 文档比较大,在高并发场景,有可能会打爆网卡。
如果使用的是 JSON 类型,相同的需求,直接使用 SQL 命令就可搞定。不仅能节省网络带宽,结合后面提到的函数索引,还能降低磁盘 IO 消耗。
mysql> create table t(c1 json); Query OK, 0 rows affected (0.09 sec) mysql> insert into t values('{ "name":"John", "age":30, "city":"New York"}'); Query OK, 1 row affected (0.01 sec) mysql> select c1->"$.age" from t; +-------------+ | c1->"$.age" | +-------------+ | 30 | +-------------+ 1 row in set (0.00 sec)
本文将从以下几个方面展开:
- 什么是 JSON。
- JSON 字段的增删改查操作。
- 如何对 JSON 字段创建索引。
- 如何将存储 JSON 字符串的字符字段升级为 JSON 字段。
- 使用 JSON 时的注意事项。
- Partial Updates。
- 其它 JSON 函数。
一、什么是 JSON
JSON 是 JavaScript Object Notation(JavaScript 对象表示法)的缩写,是一个轻量级的,基于文本的,跨语言的数据交换格式。易于阅读和编写。
JSON 的基本数据类型如下:
-
数值:十进制数,不能有前导 0,可以为负数或小数,还可以为 e 或 E 表示的指数。
-
字符串:字符串必须用双引号括起来。
-
布尔值:true,false。
-
数组:一个由零或多个值组成的有序序列。每个值可以为任意类型。数组使用方括号
[]
括起来,元素之间用逗号,
分隔。譬如,[1, "abc", null, true, "10:27:06.000000", {"id": 1}]
-
对象:一个由零或者多个键值对组成的无序集合。其中键必须是字符串,值可以为任意类型。
对象使用花括号
{}
括起来,键值对之间使用逗号,
分隔,键与值之间用冒号:
分隔。譬如,{"db": ["mysql", "oracle"], "id": 123, "info": {"age": 20}}
-
空值:null。
二、JSON 字段的增删改查操作
下面我们看看 JSON 字段常见的增删改查操作:
2.1 插入操作
可直接插入 JSON 格式的字符串。
mysql> create table t(c1 json); Query OK, 0 rows affected (0.03 sec) mysql> insert into t values('[1, "abc", null, true, "08:45:06.000000"]'); Query OK, 1 row affected (0.01 sec) mysql> insert into t values('{"id": 87, "name": "carrot"}'); Query OK, 1 row affected (0.01 sec)
也可使用函数,常用的有 JSON_ARRAY() 和 JSON_OBJECT(),前者用于构造 JSON 数组,后者用于构造 JSON 对象。如,
mysql> select json_array(1, "abc", null, true,curtime()); +--------------------------------------------+ | json_array(1, "abc", null, true,curtime()) | +--------------------------------------------+ | [1, "abc", null, true, "10:12:25.000000"] | +--------------------------------------------+ 1 row in set (0.01 sec) mysql> select json_object('id', 87, 'name', 'carrot'); +-----------------------------------------+ | json_object('id', 87, 'name', 'carrot') | +-----------------------------------------+ | {"id": 87, "name": "carrot"} | +-----------------------------------------+ 1 row in set (0.00 sec)
对于 JSON 文档,KEY 名不能重复。
如果插入的值中存在重复 KEY,在 MySQL 8.0.3 之前,遵循 first duplicate key wins 原则,会保留第一个 KEY,后面的将被丢弃掉。
从 MySQL 8.0.3 开始,遵循的是 last duplicate key wins 原则,只会保留最后一个 KEY。
下面通过一个具体的示例来看看两者的区别。
MySQL 5.7.36
mysql> select json_object('key1',10,'key2',20,'key1',30); +--------------------------------------------+ | json_object('key1',10,'key2',20,'key1',30) | +--------------------------------------------+ | {"key1": 10, "key2": 20} | +--------------------------------------------+ 1 row in set (0.02 sec)
MySQL 8.0.27
mysql> select json_object('key1',10,'key2',20,'key1',30); +--------------------------------------------+ | json_object('key1',10,'key2',20,'key1',30) | +--------------------------------------------+ | {"key1": 30, "key2": 20} | +--------------------------------------------+ 1 row in set (0.00 sec)
2.2 查询操作
JSON_EXTRACT(json_doc, path[, path] ...)
其中,json_doc 是 JSON 文档,path 是路径。该函数会从 JSON 文档提取指定路径(path)的元素。如果指定 path 不存在,会返回 NULL。可指定多个 path,匹配到的多个值会以数组形式返回。
下面我们结合一些具体的示例来看看 path 及 JSON_EXTRACT 的用法。
首先我们看看数组。
数组的路径是通过下标来表示的。第一个元素的下标是 0。
mysql> select json_extract('[10, 20, [30, 40]]', '$[0]'); +--------------------------------------------+ | json_extract('[10, 20, [30, 40]]', '$[0]') | +--------------------------------------------+ | 10 | +--------------------------------------------+ 1 row in set (0.00 sec) mysql> select json_extract('[10, 20, [30, 40]]', '$[0]', '$[1]','$[2][0]'); +--------------------------------------------------------------+ | json_extract('[10, 20, [30, 40]]', '$[0]', '$[1]','$[2][0]') | +--------------------------------------------------------------+ | [10, 20, 30] | +--------------------------------------------------------------+ 1 row in set (0.00 sec)
除此之外,还可通过 [M to N]
获取数组的子集。
mysql> select json_extract('[10, 20, [30, 40]]', '$[0 to 1]'); +-------------------------------------------------+ | json_extract('[10, 20, [30, 40]]', '$[0 to 1]') | +-------------------------------------------------+ | [10, 20] | +-------------------------------------------------+ 1 row in set (0.00 sec) # 这里的 last 代表最后一个元素的下标 mysql> select json_extract('[10, 20, [30, 40]]', '$[last-1 to last]'); +---------------------------------------------------------+ | json_extract('[10, 20, [30, 40]]', '$[last-1 to last]') | +---------------------------------------------------------+ | [20, [30, 40]] | +---------------------------------------------------------+ 1 row in set (0.00 sec)
也可通过 [*]
获取数组中的所有元素。
mysql> select json_extract('[10, 20, [30, 40]]', '$[*]'); +--------------------------------------------+ | json_extract('[10, 20, [30, 40]]', '$[*]') | +--------------------------------------------+ | [10, 20, [30, 40]] | +--------------------------------------------+ 1 row in set (0.00 sec)
接下来,我们看看对象。
对象的路径是通过 KEY 来表示的。
mysql> set @j='{"a": 1, "b": [2, 3], "a c": 4}'; Query OK, 0 rows affected (0.00 sec) # 如果 KEY 在路径表达式中不合法(譬如存在空格),则在引用这个 KEY 时,需用双引号括起来。 mysql> select json_extract(@j, '$.a'), json_extract(@j, '$."a c"'), json_extract(@j, '$.b[1]'); +-------------------------+-----------------------------+----------------------------+ | json_extract(@j, '$.a') | json_extract(@j, '$."a c"') | json_extract(@j, '$.b[1]') | +-------------------------+-----------------------------+----------------------------+ | 1 | 4 | 3 | +-------------------------+-----------------------------+----------------------------+ 1 row in set (0.00 sec)
除此之外,还可通过 .*
获取对象中的所有元素。
mysql> select json_extract('{"a": 1, "b": [2, 3], "a c": 4}', '$.*'); +--------------------------------------------------------+ | json_extract('{"a": 1, "b": [2, 3], "a c": 4}', '$.*') | +--------------------------------------------------------+ | [1, [2, 3], 4] | +--------------------------------------------------------+ 1 row in set (0.00 sec) # 这里的 $**.b 匹配 $.a.b 和 $.c.b mysql> select json_extract('{"a": {"b": 1}, "c": {"b": 2}}', '$**.b'); +---------------------------------------------------------+ | json_extract('{"a": {"b": 1}, "c": {"b": 2}}', '$**.b') | +---------------------------------------------------------+ | [1, 2] | +---------------------------------------------------------+ 1 row in set (0.00 sec)
column->path
column->path,包括后面讲到的 column->>path,都是语法糖,在实际使用的时候都会转化为 JSON_EXTRACT。
column->path 等同于 JSON_EXTRACT(column, path) ,只能指定一个path。
create table t(c2 json); insert into t values('{"empno": 1001, "ename": "jack"}'), ('{"empno": 1002, "ename": "mark"}'); mysql> select c2, c2->"$.ename" from t; +----------------------------------+---------------+ | c2 | c2->"$.ename" | +----------------------------------+---------------+ | {"empno": 1001, "ename": "jack"} | "jack" | | {"empno": 1002, "ename": "mark"} | "mark" | +----------------------------------+---------------+ 2 rows in set (0.00 sec) mysql> select * from t where c2->"$.empno" = 1001; +------+----------------------------------+ | c1 | c2 | +------+----------------------------------+ | 1 | {"empno": 1001, "ename": "jack"} | +------+----------------------------------+ 1 row in set (0.00 sec)
column->>path
同 column->path 类似,只不过其返回的是字符串。以下三者是等价的。
- JSON_UNQUOTE( JSON_EXTRACT(column, path) )
- JSON_UNQUOTE(column -> path)
- column->>path
mysql> select c2->'$.ename',json_extract(c2, "$.ename"),json_unquote(c2->'$.ename'),c2->>'$.ename' from t; +---------------+-----------------------------+-----------------------------+----------------+ | c2->'$.ename' | json_extract(c2, "$.ename") | json_unquote(c2->'$.ename') | c2->>'$.ename' | +---------------+-----------------------------+-----------------------------+----------------+ | "jack" | "jack" | jack | jack | | "mark" | "mark" | mark | mark | +---------------+-----------------------------+-----------------------------+----------------+ 2 rows in set (0.00 sec)
2.3 修改操作
JSON_INSERT(json_doc, path, val[, path, val] ...)
插入新值。
仅当指定位置或指定 KEY 的值不存在时,才执行插入操作。另外,如果指定的 path 是数组下标,且 json_doc 不是数组,该函数首先会将 json_doc 转化为数组,然后再插入新值。
下面我们看几个示例。
mysql> select json_insert('1','$[0]',"10"); +------------------------------+ | json_insert('1','$[0]',"10") | +------------------------------+ | 1 | +------------------------------+ 1 row in set (0.00 sec) mysql> select json_insert('1','$[1]',"10"); +------------------------------+ | json_insert('1','$[1]',"10") | +------------------------------+ | [1, "10"] | +------------------------------+ 1 row in set (0.01 sec) mysql> select json_insert('["1","2"]','$[2]',"10"); +--------------------------------------+ | json_insert('["1","2"]','$[2]',"10") | +--------------------------------------+ | ["1", "2", "10"] | +--------------------------------------+ 1 row in set (0.00 sec) mysql> set @j = '{ "a": 1, "b": [2, 3]}'; Query OK, 0 rows affected (0.00 sec) mysql> select json_insert(@j, '$.a', 10, '$.c', '[true, false]'); +----------------------------------------------------+ | json_insert(@j, '$.a', 10, '$.c', '[true, false]') | +----------------------------------------------------+ | {"a": 1, "b": [2, 3], "c": "[true, false]"} | +----------------------------------------------------+ 1 row in set (0.00 sec)
JSON_SET(json_doc, path, val[, path, val] ...)
插入新值,并替换已经存在的值。
换言之,如果指定位置或指定 KEY 的值不存在,会执行插入操作,如果存在,则执行更新操作。
mysql> set @j = '{ "a": 1, "b": [2, 3]}'; Query OK, 0 rows