主頁 > 資料庫 > 一文說透 MySQL JSON 資料型別(收藏)

一文說透 MySQL JSON 資料型別(收藏)

2022-05-06 08:11:41 資料庫

JSON 資料型別是 MySQL 5.7.8 開始支持的,在此之前,只能通過字符型別(CHAR,VARCHAR 或 TEXT )來保存 JSON 檔案,

相對字符型別,原生的 JSON 型別具有以下優勢:

  1. 在插入時能自動校驗檔案是否滿足 JSON 格式的要求,
  2. 優化了存盤格式,無需讀取整個檔案就能快速訪問某個元素的值,

在 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)

本文將從以下幾個方面展開:

  1. 什么是 JSON,
  2. JSON 欄位的增刪改查操作,
  3. 如何對 JSON 欄位創建索引,
  4. 如何將存盤 JSON 字串的字符欄位升級為 JSON 欄位,
  5. 使用 JSON 時的注意事項,
  6. Partial Updates,
  7. 其它 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 affected (0.00 sec)

mysql> select json_set(@j, '$.a', 10, '$.c', '[true, false]');
+-------------------------------------------------+
| json_set(@j, '$.a', 10, '$.c', '[true, false]') |
+-------------------------------------------------+
| {"a": 10, "b": [2, 3], "c": "[true, false]"}    |
+-------------------------------------------------+
1 row in set (0.00 sec)

JSON_REPLACE(json_doc, path, val[, path, val] ...)

替換已經存在的值,

mysql> set @j = '{ "a": 1, "b": [2, 3]}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_replace(@j, '$.a', 10, '$.c', '[true, false]');
+-----------------------------------------------------+
| json_replace(@j, '$.a', 10, '$.c', '[true, false]') |
+-----------------------------------------------------+
| {"a": 10, "b": [2, 3]}                              |
+-----------------------------------------------------+
1 row in set (0.00 sec)

2.4 洗掉操作

JSON_REMOVE(json_doc, path[, path] ...)

洗掉 JSON 檔案指定位置的元素,

mysql> set @j = '{ "a": 1, "b": [2, 3]}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_remove(@j, '$.a');
+------------------------+
| JSON_REMOVE(@j, '$.a') |
+------------------------+
| {"b": [2, 3]}          |
+------------------------+
1 row in set (0.00 sec)

mysql> set @j = '["a", ["b", "c"], "d", "e"]';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_remove(@j, '$[1]');
+-------------------------+
| JSON_REMOVE(@j, '$[1]') |
+-------------------------+
| ["a", "d", "e"]         |
+-------------------------+
1 row in set (0.00 sec)

mysql> select json_remove(@j, '$[1]','$[2]');
+--------------------------------+
| JSON_REMOVE(@j, '$[1]','$[2]') |
+--------------------------------+
| ["a", "d"]                     |
+--------------------------------+
1 row in set (0.00 sec)

mysql> select json_remove(@j, '$[1]','$[1]');
+--------------------------------+
| JSON_REMOVE(@j, '$[1]','$[1]') |
+--------------------------------+
| ["a", "e"]                     |
+--------------------------------+
1 row in set (0.00 sec)

最后一個查詢,雖然兩個 path 都是 '$[1]' ,但作用物件不一樣,第一個 path 的作用物件是 '["a", ["b", "c"], "d", "e"]' ,第二個 path 的作用物件是洗掉了 '$[1]' 后的陣列,即 '["a", "d", "e"]' ,

 

三、如何對 JSON 欄位創建索引

同 TEXT,BLOB 欄位一樣,JSON 欄位不允許直接創建索引,

mysql> create table t(c1 json, index (c1));
ERROR 3152 (42000): JSON column 'c1' supports indexing only via generated columns on a specified JSON path.

即使支持,實際意義也不大,因為我們一般是基于檔案中的元素進行查詢,很少會基于整個  JSON 檔案,

對檔案中的元素進行查詢,就需要用到 MySQL 5.7 引入的虛擬列及函式索引,

下面我們來看一個具體的示例,

# C2 即虛擬列
# index (c2) 對虛擬列添加索引,
create table t ( c1 json, c2 varchar(10) as (JSON_UNQUOTE(c1 -> "$.name")), index (c2) );

insert into t (c1) values  ('{"id": 1, "name": "a"}'), ('{"id": 2, "name": "b"}'), ('{"id": 3, "name": "c"}'), ('{"id": 4, "name": "d"}');

mysql> explain select * from t where c2 = 'a';
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t     | NULL       | ref  | c2            | c2   | 43      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t where c1->'$.name' = 'a';
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t     | NULL       | ref  | c2            | c2   | 43      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

可以看到,無論是使用虛擬列,還是檔案中的元素來查詢,都可以利用上索引,

注意,在創建虛擬列時需指定  JSON_UNQUOTE,將 c1 -> "$.name" 的回傳值轉換為字串,

 

四、如何將存盤 JSON 字串的字符欄位升級為 JSON 欄位

在 MySQL 支持 JSON 型別之前,對于 JSON 檔案,一般是以字串的形式存盤在字符型別(VARCHAR 或 TEXT)中,

在 JSON 型別出來之后,如何將這些字符欄位升級為 JSON 欄位呢?

為方便演示,這里首先構建測驗資料,

create table t (id int auto_increment primary key, c1 text);

insert into t (c1) values ('{"id": "1", "name": "a"}'), ('{"id": "2", "name": "b"}'), ('{"id": "3", "name": "c"}'), ('{"id", "name": "d"}');

注意,最后一個檔案有問題,不是合格的 JSON 檔案,

如果使用 DDL 直接修改欄位的資料型別,會報錯,

mysql> alter table t modify c1 json;
ERROR 3140 (22032): Invalid JSON text: "Missing a colon after a name of object member." at position 5 in value for column '#sql-7e1c_1f6.c1'.

下面,我們看看具體的升級步驟,

(1)使用 json_valid 函式找出不滿足 JSON 格式要求的檔案,

mysql> select * from t where json_valid(c1) = 0;
+----+---------------------+
| id | c1                  |
+----+---------------------+
|  4 | {"id", "name": "d"} |
+----+---------------------+
1 row in set (0.00 sec)

(2)處理不滿足 JSON 格式要求的檔案,

mysql> update t set c1='{"id": "4", "name": "d"}' where id=4;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

(3)將 TEXT 欄位修改為 JSON 欄位,

mysql> select * from t where json_valid(c1) = 0;
Empty set (0.00 sec)

mysql> alter table t modify c1 json;
Query OK, 4 rows affected (0.13 sec)
Records: 4  Duplicates: 0  Warnings: 0

 

五、使用 JSON 時的注意事項

對于 JSON 型別,有以下幾點需要注意:

  1. 在 MySQL 8.0.13 之前,不允許對 BLOB,TEXT,GEOMETRY,JSON 欄位設定默認值,從 MySQL 8.0.13 開始,取消了這個限制,

    設定時,注意默認值需通過小括號()括起來,否則的話,還是會提示 JSON 欄位不允許設定默認值,

    mysql> create table t(c1 json not null default (''));
    Query OK, 0 rows affected (0.03 sec)

    mysql> create table t(c1 json not null default '');
    ERROR 1101 (42000): BLOB, TEXT, GEOMETRY or JSON column 'c1' can't have a default value
  2. 不允許直接創建索引,可創建函式索引,

  3. JSON 列的最大大小和 LONGBLOB(LONGTEXT)一樣,都是 4G,

  4. 插入時,單個檔案的大小受到 max_allowed_packet 的限制,該引數最大是 1G,

 

六、Partial Updates

在 MySQL 5.7 中,對 JSON 檔案進行更新,其處理策略是,洗掉舊的檔案,再插入新的檔案,即使這個修改很微小,只涉及幾個位元組,也會替換掉整個檔案,很顯然,這種處理方式的效率較為低下,

在 MySQL 8.0 中,針對 JSON 檔案,引入了一項新的特性-Partial Updates(部分更新),支持 JSON 檔案的原地更新,得益于這個特性,JSON 檔案的處理性能得到了極大提升,

下面我們具體來看看,

6.1 使用 Partial Updates 的條件

為方便闡述,這里先構造測驗資料,

create table t (id int auto_increment primary key, c1 json);

insert into t (c1) values  ('{"id": 1, "name": "a"}'), ('{"id": 2, "name": "b"}'), ('{"id": 3, "name": "c"}'), ('{"id": 4, "name": "d"}');

mysql> select * from t;
+----+------------------------+
| id | c1                     |
+----+------------------------+
|  1 | {"id": 1, "name": "a"} |
|  2 | {"id": 2, "name": "b"} |
|  3 | {"id": 3, "name": "c"} |
|  4 | {"id": 4, "name": "d"} |
+----+------------------------+
4 rows in set (0.00 sec)

使用 Partial Updates 需滿足以下條件:

  1. 被更新的列是 JSON 型別,

  2. 使用 JSON_SET,JSON_REPLACE,JSON_REMOVE 進行 UPDATE 操作,如,

    update t set c1=json_remove(c1,'$.id') where id=1;

    不使用這三個函式,而顯式賦值,就不會進行部分更新,如,

    update t set c1='{"id": 1, "name": "a"}' where id=1;
  3. 輸入列和目標列必須是同一列,如,

    update t set c1=json_replace(c1,'$.id',10) where id=1;

    否則的話,就不會進行部分更新,如,

    update t set c1=json_replace(c2,'$.id',10) where id=1;
  4. 變更前后,JSON 檔案的空間使用不會增加,

關于最后一個條件,我們看看下面這個示例,

mysql> select *,json_storage_size(c1),json_storage_free(c1) from t where id=1;
+----+------------------------+-----------------------+-----------------------+
| id | c1                     | json_storage_size(c1) | json_storage_free(c1) |
+----+------------------------+-----------------------+-----------------------+
|  1 | {"id": 1, "name": "a"} |                    27 |                     0 |
+----+------------------------+-----------------------+-----------------------+
1 row in set (0.00 sec)

mysql> update t set c1=json_remove(c1,'$.id') where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select *,json_storage_size(c1),json_storage_free(c1) from t where id=1;
+----+---------------+-----------------------+-----------------------+
| id | c1            | json_storage_size(c1) | json_storage_free(c1) |
+----+---------------+-----------------------+-----------------------+
|  1 | {"name": "a"} |                    27 |                     9 |
+----+---------------+-----------------------+-----------------------+
1 row in set (0.00 sec)

mysql> update t set c1=json_set(c1,'$.id',3306) where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select *,json_storage_size(c1),json_storage_free(c1) from t where id=1;
+----+---------------------------+-----------------------+-----------------------+
| id | c1                        | json_storage_size(c1) | json_storage_free(c1) |
+----+---------------------------+-----------------------+-----------------------+
|  1 | {"id": 3306, "name": "a"} |                    27 |                     0 |
+----+---------------------------+-----------------------+-----------------------+
1 row in set (0.00 sec)

mysql> update t set c1=json_set(c1,'$.id','mysql') where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select *,json_storage_size(c1),json_storage_free(c1) from t where id=1;
+----+------------------------------+-----------------------+-----------------------+
| id | c1                           | json_storage_size(c1) | json_storage_free(c1) |
+----+------------------------------+-----------------------+-----------------------+
|  1 | {"id": "mysql", "name": "a"} |                    33 |                     0 |
+----+------------------------------+-----------------------+-----------------------+
1 row in set (0.00 sec)

示例中,用到了兩個函式:JSON_STORAGE_SIZE 和 JSON_STORAGE_FREE ,前者用來獲取 JSON 檔案的空間使用情況,后者用來獲取 JSON 檔案在執行原地更新后的空間釋放情況,

這里一共執行了三次 UPDATE 操作,前兩次是原地更新,第三次不是,同樣是 JSON_SET 操作,為什么第一次是原地更新,而第二次不是呢?

因為第一次的 JSON_SET 復用了 JSON_REMOVE 釋放的空間,而第二次的 JSON_SET 執行的是更新操作,且 'mysql' 比 3306 需要更多的存盤空間,


6.2 如何在 binlog 中開啟 Partial Updates

Partial Updates 不僅僅適用于存盤引擎層,還可用于主從復制場景,

主從復制開啟 Partial Updates,只需將引數 binlog_row_value_options(默認為空)設定為 PARTIAL_JSON,

下面具體來看看,同一個 UPDATE 操作,開啟和不開啟 Partial Updates,在 binlog 中的記錄有何區別,

update t set c1=json_replace(c1,'$.id',10) where id=1;

不開啟

### UPDATE `slowtech`.`t`
### WHERE
###   @1=1
###   @2='{"id": "1", "name": "a"}'
### SET
###   @1=1
###   @2='{"id": 10, "name": "a"}'

開啟

### UPDATE `slowtech`.`t`
### WHERE
###   @1=1
###   @2='{"id": 1, "name": "a"}'
### SET
###   @1=1
###   @2=JSON_REPLACE(@2, '$.id', 10)

對比 binlog 的內容,可以看到,不開啟,無論是修改前的鏡像(before_image)還是修改后的鏡像(after_image),記錄的都是完整檔案,而開啟后,對于修改后的鏡像,記錄的是命令,而不是完整檔案,這樣可節省近一半的空間,

在將 binlog_row_value_options 設定為 PARTIAL_JSON 后,對于可使用 Partial Updates 的操作,在 binlog 中,不再通過 ROWS_EVENT 來記錄,而是新增了一個 PARTIAL_UPDATE_ROWS_EVENT 的事件型別,

需要注意的是,binlog 中使用 Partial Updates,只需滿足存盤引擎層使用 Partial Updates 的前三個條件,無需考慮變更前后,JSON 檔案的空間使用是否會增加,

6.3 關于 Partial Updates 的性能測驗

首先構造測驗資料,t 表一共有 16 個檔案,每個檔案近 10 MB,

create table t(id int auto_increment primary key,
               json_col json,
               name varchar(100) as (json_col->>'$.name'),
               age int as (json_col->'$.age'));

insert into t(json_col) values
(json_object('name', 'Joe', 'age', 24,
             'data', repeat('x', 10 * 1000 * 1000))),
(json_object('name', 'Sue', 'age', 32,
             'data', repeat('y', 10 * 1000 * 1000))),
(json_object('name', 'Pete', 'age', 40,
             'data', repeat('z', 10 * 1000 * 1000))),
(json_object('name', 'Jenny', 'age', 27,
             'data', repeat('w', 10 * 1000 * 1000)));

insert into t(json_col) select json_col from t;
insert into t(json_col) select json_col from t;

接下來,測驗下述 SQL

update t set json_col = json_set(json_col, '$.age', age + 1);

在以下四種場景下的執行時間:

  1. MySQL 5.7.36
  2. MySQL 8.0.27
  3. MySQL 8.0.27,binlog_row_value_options=PARTIAL_JSON
  4. MySQL 8.0.27,binlog_row_value_options=PARTIAL_JSON + binlog_row_image=MINIMAL

分別執行 10 次,去掉最大值和最小值后求平均值,

最后的測驗結果如下:

圖片

以 MySQL 5.7.36 的查詢時間作為基準:

  1. MySQL 8.0 只開啟存盤引擎層的 Partial Updates,查詢時間比 MySQL 5.7 快 1.94 倍,
  2. MySQL 8.0 同時開啟存盤引擎層和 binlog 中的 Partial Updates,查詢時間比 MySQL 5.7 快 4.87 倍,
  3. 如果在 2 的基礎上,同時將 binlog_row_image 設定為 MINIMAL,查詢時間更是比 MySQL 5.7 快 102.22 倍,

當然,在生產環境,我們一般很少將 binlog_row_image 設定為 MINIMAL,

但即使如此,只開啟存盤引擎層和 binlog 中的 Partial Updates,查詢時間也比 MySQL 5.7 快 4.87 倍,性能提升還是比較明顯的,

 

七、其它 JSON 函式

7.1 查詢相關

JSON_CONTAINS(target, candidate[, path])

判斷 target 檔案是否包含 candidate 檔案,如果包含,則回傳 1,否則是 0,

mysql> set @j = '{"a": [1, 2], "b": 3, "c": {"d": 4}}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_contains(@j, '1', '$.a'),json_contains(@j, '1', '$.b');
+-------------------------------+-------------------------------+
| json_contains(@j, '1', '$.a') | json_contains(@j, '1', '$.b') |
+-------------------------------+-------------------------------+
|                             1 |                             0 |
+-------------------------------+-------------------------------+
1 row in set (0.00 sec)

mysql> select json_contains(@j,'{"d": 4}','$.a'),json_contains(@j,'{"d": 4}','$.c');
+------------------------------------+------------------------------------+
| json_contains(@j,'{"d": 4}','$.a') | json_contains(@j,'{"d": 4}','$.c') |
+------------------------------------+------------------------------------+
|                                  0 |                                  1 |
+------------------------------------+------------------------------------+
1 row in set (0.00 sec)

JSON_CONTAINS_PATH(json_doc, one_or_all, path[, path] ...)

判斷指定的 path 是否存在,存在,則回傳 1,否則是 0,

函式中的 one_or_all 可指定 one 或 all,one 是任意一個路徑存在就回傳 1,all 是所有路徑都存在才回傳 1,

mysql> set @j = '{"a": [1, 2], "b": 3, "c": {"d": 4}}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_contains_path(@j, 'one', '$.a', '$.e'), json_contains_path(@j, 'all', '$.a', '$.e');
+---------------------------------------------+---------------------------------------------+
| json_contains_path(@j, 'one', '$.a', '$.e') | json_contains_path(@j, 'all', '$.a', '$.e') |
+---------------------------------------------+---------------------------------------------+
|                                           1 |                                           0 |
+---------------------------------------------+---------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_contains_path(@j, 'one', '$.c.d'),json_contains_path(@j, 'one', '$.a.d');
+----------------------------------------+----------------------------------------+
| json_contains_path(@j, 'one', '$.c.d') | json_contains_path(@j, 'one', '$.a.d') |
+----------------------------------------+----------------------------------------+
|                                      1 |                                      0 |
+----------------------------------------+----------------------------------------+
1 row in set (0.00 sec)

JSON_SEARCH(json_doc, one_or_all, search_str[, escape_char[, path] ...])

回傳某個字串(search_str)在 JSON 檔案中的位置,其中,

  • one_or_all:匹配的次數,one 是只匹配一次,all 是匹配所有,如果匹配到多個,結果會以陣列的形式回傳,
  • search_str:子串,支持模糊匹配:% 和 _ ,
  • escape_char:轉義符,如果該引數不填或為 NULL,則取默認轉義符\
  • path:查找路徑,
mysql> set @j = '["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_search(@j, 'one', 'abc'),json_search(@j, 'all', 'abc'),json_search(@j, 'all', 'ghi');
+-------------------------------+-------------------------------+-------------------------------+
| json_search(@j, 'one', 'abc') | json_search(@j, 'all', 'abc') | json_search(@j, 'all', 'ghi') |
+-------------------------------+-------------------------------+-------------------------------+
| "$[0]"                        | ["$[0]", "$[2].x"]            | NULL                          |
+-------------------------------+-------------------------------+-------------------------------+
1 row in set (0.00 sec)

mysql> select json_search(@j, 'all', '%b%', NULL, '$[1]'), json_search(@j, 'all', '%b%', NULL, '$[3]');
+---------------------------------------------+---------------------------------------------+
| json_search(@j, 'all', '%b%', NULL, '$[1]') | json_search(@j, 'all', '%b%', NULL, '$[3]') |
+---------------------------------------------+---------------------------------------------+
| NULL                                        | "$[3].y"                                    |
+---------------------------------------------+---------------------------------------------+
1 row in set (0.00 sec)

JSON_KEYS(json_doc[, path])

回傳 JSON 檔案最外層的 key,如果指定了 path,則回傳該 path 對應元素最外層的 key,

mysql> select json_keys('{"a": 1, "b": {"c": 30}}');
+---------------------------------------+
| json_keys('{"a": 1, "b": {"c": 30}}') |
+---------------------------------------+
| ["a", "b"]                            |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> select json_keys('{"a": 1, "b": {"c": 30}}', '$.b');
+----------------------------------------------+
| json_keys('{"a": 1, "b": {"c": 30}}', '$.b') |
+----------------------------------------------+
| ["c"]                                        |
+----------------------------------------------+
1 row in set (0.00 sec)

JSON_VALUE(json_doc, path)

8.0.21 引入的,從 JSON 檔案提取指定路徑(path)的元素,

該函式的完整語法如下:

JSON_VALUE(json_doc, path [RETURNING type] [on_empty] [on_error])

on_empty:
    {NULL | ERROR | DEFAULT value} ON EMPTY

on_error:
    {NULL | ERROR | DEFAULT value} ON ERROR

其中:

  • RETURNING type:回傳值的型別,不指定,則默認是 VARCHAR(512),不指定字符集,則默認是 utf8mb4,且區分大小寫,
  • on_empty:如果指定路徑沒有值,會觸發 on_empty 子句, 默認是回傳 NULL,也可指定 ERROR 拋出錯誤,或者通過 DEFAULT value 回傳默認值,
  • on_error:三種情況下會觸發 on_error 子句:從陣列或物件中提取元素時,會決議到多個值;型別轉換錯誤,譬如將 "abc" 轉換為 unsigned 型別;值被 truncate 了,默認是回傳 NULL,
mysql> select json_value('{"item": "shoes", "price": "49.95"}', '$.item');
+-------------------------------------------------------------+
| json_value('{"item": "shoes", "price": "49.95"}', '$.item') |
+-------------------------------------------------------------+
| shoes                                                       |
+-------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_value('{"item": "shoes", "price": "49.95"}', '$.price' returning decimal(4,2)) as price;
+-------+
| price |
+-------+
| 49.95 |
+-------+
1 row in set (0.00 sec)

mysql> select json_value('{"item": "shoes", "price": "49.95"}', '$.price1' error on empty);
ERROR 3966 (22035): No value was found by 'json_value' on the specified path.

mysql> select json_value('[1, 2, 3]', '$[1 to 2]' error on error);
ERROR 3967 (22034): More than one value was found by 'json_value' on the specified path.

mysql> select json_value('{"item": "shoes", "price": "49.95"}', '$.item' returning unsigned error on error) as price;
ERROR 1690 (22003): UNSIGNED value is out of range in 'json_value'

value MEMBER OF(json_array)

判斷 value 是否是 JSON 陣列的一個元素,如果是,則回傳 1,否則是 0,

mysql> select 17 member of('[23, "abc", 17, "ab", 10]');
+-------------------------------------------+
| 17 member of('[23, "abc", 17, "ab", 10]') |
+-------------------------------------------+
|                                         1 |
+-------------------------------------------+
1 row in set (0.00 sec)

mysql> select cast('[4,5]' as json) member of('[[3,4],[4,5]]');
+--------------------------------------------------+
| cast('[4,5]' as json) member of('[[3,4],[4,5]]') |
+--------------------------------------------------+
|                                                1 |
+--------------------------------------------------+
1 row in set (0.00 sec)

JSON_OVERLAPS(json_doc1, json_doc2)

MySQL 8.0.17 引入的,用來比較兩個 JSON 檔案是否有相同的鍵值對或陣列元素,如果有,則回傳 1,否則是 0,如果兩個引數都是標量,則判斷這兩個標量是否相等,

mysql> select json_overlaps('[1,3,5,7]', '[2,5,7]'),json_overlaps('[1,3,5,7]', '[2,6,8]');
+---------------------------------------+---------------------------------------+
| json_overlaps('[1,3,5,7]', '[2,5,7]') | json_overlaps('[1,3,5,7]', '[2,6,8]') |
+---------------------------------------+---------------------------------------+
|                                     1 |                                     0 |
+---------------------------------------+---------------------------------------+
1 row in set (0.00 sec)

mysql> select json_overlaps('{"a":1,"b":2}', '{"c":3,"d":4,"b":2}');
+-------------------------------------------------------+
| json_overlaps('{"a":1,"b":2}', '{"c":3,"d":4,"b":2}') |
+-------------------------------------------------------+
|                                                     1 |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_overlaps('{"a":1,"b":2}', '{"c":3,"d":4,"b":10}');
+--------------------------------------------------------+
| json_overlaps('{"a":1,"b":2}', '{"c":3,"d":4,"b":10}') |
+--------------------------------------------------------+
|                                                      0 |
+--------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_overlaps('5', '5'),json_overlaps('5', '6');
+-------------------------+-------------------------+
| json_overlaps('5', '5') | json_overlaps('5', '6') |
+-------------------------+-------------------------+
|                       1 |                       0 |
+-------------------------+-------------------------+
1 row in set (0.00 sec)

從 MySQL 8.0.17 開始,InnoDB 支持多值索引,可用在 JSON 陣列中,當我們使用 JSON_CONTAINS、MEMBER OF、JSON_OVERLAPS 進行陣列相關的操作時,可使用多值索引來加快查詢,


7.2 修改相關

JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ...)

向陣列指定位置追加元素,如果指定 path 不存在,則不添加,

mysql> set @j = '["a", ["b", "c"], "d"]';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_array_append(@j, '$[0]', 1, '$[1][0]', 2, '$[3]', 3);
+-----------------------------------------------------------+
| json_array_append(@j, '$[0]', 1, '$[1][0]', 2, '$[3]', 3) |
+-----------------------------------------------------------+
| [["a", 1], [["b", 2], "c"], "d"]                          |
+-----------------------------------------------------------+
1 row in set (0.00 sec)

mysql> set @j = '{"a": 1, "b": [2, 3], "c": 4}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_array_append(@j, '$.b', 'x', '$', 'z');
+---------------------------------------------+
| json_array_append(@j, '$.b', 'x', '$', 'z') |
+---------------------------------------------+
| [{"a": 1, "b": [2, 3, "x"], "c": 4}, "z"]   |
+---------------------------------------------+
1 row in set (0.00 sec)

JSON_ARRAY_INSERT(json_doc, path, val[, path, val] ...)

向陣列指定位置插入元素,

mysql> set @j = '["a", ["b", "c"],{"d":"e"}]';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_array_insert(@j, '$[0]', 1);
+----------------------------------+
| json_array_insert(@j, '$[0]', 1) |
+----------------------------------+
| [1, "a", ["b", "c"], {"d": "e"}] |
+----------------------------------+
1 row in set (0.00 sec)

mysql> select json_array_insert(@j, '$[1]', cast('[1,2]' as json));
+------------------------------------------------------+
| json_array_insert(@j, '$[1]', cast('[1,2]' as json)) |
+------------------------------------------------------+
| ["a", [1, 2], ["b", "c"], {"d": "e"}]                |
+------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_array_insert(@j, '$[5]', 2);
+----------------------------------+
| json_array_insert(@j, '$[5]', 2) |
+----------------------------------+
| ["a", ["b", "c"], {"d": "e"}, 2] |
+----------------------------------+
1 row in set (0.00 sec)

JSON_MERGE_PATCH(json_doc, json_doc[, json_doc] ...)

MySQL 8.0.3 引入的,用來合并多個 JSON 檔案,其合并規則如下:

  1. 如果兩個檔案不全是 JSON 物件,則合并后的結果是第二個檔案,
  2. 如果兩個檔案都是 JSON 物件,且不存在著同名 KEY,則合并后的檔案包括兩個檔案的所有元素,如果存在著同名 KEY,則第二個檔案的值會覆寫第一個,
mysql> select json_merge_patch('[1, 2]', '[3, 4]'), json_merge_patch('[1, 2]', '{"a": 123}');
+--------------------------------------+------------------------------------------+
| json_merge_patch('[1, 2]', '[3, 4]') | json_merge_patch('[1, 2]', '{"a": 123}') |
+--------------------------------------+------------------------------------------+
| [3, 4]                               | {"a": 123}                               |
+--------------------------------------+------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_merge_patch('{"a": 1}', '{"b": 2}'),json_merge_patch('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }');
+------------------------------------------+-----------------------------------------------------------+
| json_merge_patch('{"a": 1}', '{"b": 2}') | json_merge_patch('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }') |
+------------------------------------------+-----------------------------------------------------------+
| {"a": 1, "b": 2}                         | {"a": 3, "b": 2, "c": 4}                                  |
+------------------------------------------+-----------------------------------------------------------+
1 row in set (0.00 sec)

# 如果第二個檔案存在 null 值,檔案合并后不會輸出對應的 KEY,
mysql> select json_merge_patch('{"a":1, "b":2}', '{"a":3, "b":null}');
+---------------------------------------------------------+
| json_merge_patch('{"a":1, "b":2}', '{"a":3, "b":null}') |
+---------------------------------------------------------+
| {"a": 3}                                                |
+---------------------------------------------------------+
1 row in set (0.00 sec)

JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc] ...)

MySQL 8.0.3 引入的,用來代替 JSON_MERGE,也是用來合并檔案,但合并規則與 JSON_MERGE_PATCH 有所不同,

  1. 兩個檔案中,只要有一個檔案是陣列,則另外一個檔案會合并到該陣列中,
  2. 兩個檔案都是 JSON 物件,若存在著同名 KEY ,第二個檔案并不會覆寫第一個,而是會將值 append 到第一個檔案中,
mysql> select json_merge_preserve('1','2'),json_merge_preserve('[1, 2]', '[3, 4]');
+------------------------------+-----------------------------------------+
| json_merge_preserve('1','2') | json_merge_preserve('[1, 2]', '[3, 4]') |
+------------------------------+-----------------------------------------+
| [1, 2]                       | [1, 2, 3, 4]                            |
+------------------------------+-----------------------------------------+
1 row in set (0.00 sec)

mysql> select json_merge_preserve('[1, 2]', '{"a": 123}'), json_merge_preserve('{"a": 123}', '[3,4]');
+---------------------------------------------+--------------------------------------------+
| json_merge_preserve('[1, 2]', '{"a": 123}') | json_merge_preserve('{"a": 123}', '[3,4]') |
+---------------------------------------------+--------------------------------------------+
| [1, 2, {"a": 123}]                          | [{"a": 123}, 3, 4]                         |
+---------------------------------------------+--------------------------------------------+
1 row in set (0.00 sec)

mysql> select json_merge_preserve('{"a": 1}', '{"b": 2}'), json_merge_preserve('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }');
+---------------------------------------------+--------------------------------------------------------------+
| json_merge_preserve('{"a": 1}', '{"b": 2}') | json_merge_preserve('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }') |
+---------------------------------------------+--------------------------------------------------------------+
| {"a": 1, "b": 2}                            | {"a": [1, 3], "b": 2, "c": 4}                                |
+---------------------------------------------+--------------------------------------------------------------+
1 row in set (0.00 sec)

JSON_MERGE(json_doc, json_doc[, json_doc] ...)

與 JSON_MERGE_PRESERVE 作用一樣,從 MySQL 8.0.3 開始不建議使用,后續會移除,


7.3 其它輔助函式

JSON_QUOTE(string)

生成有效的 JSON 字串,主要是對一些特殊字符(如雙引號)進行轉義,

mysql> select json_quote('null'), json_quote('"null"'), json_quote('[1, 2, 3]');
+--------------------+----------------------+-------------------------+
| json_quote('null') | json_quote('"null"') | json_quote('[1, 2, 3]') |
+--------------------+----------------------+-------------------------+
| "null"             | "\"null\""           | "[1, 2, 3]"             |
+--------------------+----------------------+-------------------------+
1 row in set (0.00 sec)

除此之外,也可通過 CAST(value AS JSON) 進行型別轉換,


JSON_UNQUOTE(json_val)

將 JSON 轉義成字串輸出,

mysql> select c2->'$.ename',json_unquote(c2->'$.ename'),
    -> json_valid(c2->'$.ename'),json_valid(json_unquote(c2->'$.ename')) from t;
+---------------+-----------------------------+---------------------------+-----------------------------------------+
| c2->'$.ename' | json_unquote(c2->'$.ename') | json_valid(c2->'$.ename') | json_valid(json_unquote(c2->'$.ename')) |
+---------------+-----------------------------+---------------------------+-----------------------------------------+
| "jack"        | jack                        |                         1 |                                       0 |
| "mark"        | mark                        |                         1 |                                       0 |
+---------------+-----------------------------+---------------------------+-----------------------------------------+
2 rows in set (0.00 sec)

直觀地看,沒加 JSON_UNQUOTE 字串會用雙引號引起來,加了 JSON_UNQUOTE 就沒有,但本質上,前者是 JSON 中的 STRING 型別,后者是 MySQL 中的字符型別,這一點可通過 JSON_VALID 來判斷,


JSON_OBJECTAGG(key, value)

取表中的兩列作為引數,其中,第一列是 key,第二列是 value,回傳 JSON 物件,如,

mysql> select * from emp;
+--------+----------+--------+
| deptno | ename    | sal    |
+--------+----------+--------+
|     10 | emp_1001 | 100.00 |
|     10 | emp_1002 | 200.00 |
|     20 | emp_1003 | 300.00 |
|     20 | emp_1004 | 400.00 |
+--------+----------+--------+
4 rows in set (0.00 sec)

mysql> select json_objectagg(ename,sal) from emp;
+----------------------------------------------------------------------------------+
| json_objectagg(ename,sal)                                                        |
+----------------------------------------------------------------------------------+
| {"emp_1001": 100.00, "emp_1002": 200.00, "emp_1003": 300.00, "emp_1004": 400.00} |
+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select deptno,json_objectagg(ename,sal) from emp group by deptno;
+--------+------------------------------------------+
| deptno | json_objectagg(ename,sal)                |
+--------+------------------------------------------+
|     10 | {"emp_1001": 100.00, "emp_1002": 200.00} |
|     20 | {"emp_1003": 300.00, "emp_1004": 400.00} |
+--------+------------------------------------------+
2 rows in set (0.00 sec)

JSON_ARRAYAGG(col_or_expr)

將列的值聚合成 JSON 陣列,注意,JSON 陣列中元素的順序是隨機的,

mysql> select json_arrayagg(ename) from emp;
+--------------------------------------------------+
| json_arrayagg(ename)                             |
+--------------------------------------------------+
| ["emp_1001", "emp_1002", "emp_1003", "emp_1004"] |
+--------------------------------------------------+
1 row in set (0.00 sec)

mysql> select deptno,json_arrayagg(ename) from emp group by deptno;
+--------+--------------------------+
| deptno | json_arrayagg(ename)     |
+--------+--------------------------+
|     10 | ["emp_1001", "emp_1002"] |
|     20 | ["emp_1003", "emp_1004"] |
+--------+--------------------------+
2 rows in set (0.00 sec)

JSON_PRETTY(json_val)

將 JSON 格式化輸出,

mysql> select json_pretty("[1,3,5]");
+------------------------+
| json_pretty("[1,3,5]") |
+------------------------+
| [
  1,
  3,
  5
]      |
+------------------------+
1 row in set (0.00 sec)

mysql> select json_pretty('{"a":"10","b":"15","x":"25"}');
+---------------------------------------------+
| json_pretty('{"a":"10","b":"15","x":"25"}') |
+---------------------------------------------+
| {
  "a": "10",
  "b": "15",
  "x": "25"
}   |
+---------------------------------------------+
1 row in set (0.00 sec)

JSON_STORAGE_FREE(json_val)

MySQL 8.0 新增的,與 Partial Updates 有關,用于計算 JSON 檔案在進行部分更新后的剩余空間,


JSON_STORAGE_SIZE(json_val)

MySQL 5.7.22 引入的,用于計算 JSON 檔案的空間使用情況,


JSON_DEPTH(json_doc)

回傳 JSON 檔案的最大深度,對于空陣列,空物件,標量值,其深度為 1,

mysql> select json_depth('{}'),json_depth('[10, 20]'),json_depth('[10, {"a": 20}]');
+------------------+------------------------+-------------------------------+
| json_depth('{}') | json_depth('[10, 20]') | json_depth('[10, {"a": 20}]') |
+------------------+------------------------+-------------------------------+
|                1 |                      2 |                             3 |
+------------------+------------------------+-------------------------------+
1 row in set (0.00 sec)

JSON_LENGTH(json_doc[, path])

回傳 JSON 檔案的長度,其計算規則如下:

  1. 如果是標量值,其長度為 1,
  2. 如果是陣列,其長度為陣列元素的個數,
  3. 如果是物件,其長度為物件元素的個數,
  4. 不包括嵌套資料和嵌套物件的長度,
mysql> select json_length('"abc"');
+----------------------+
| json_length('"abc"') |
+----------------------+
|                    1 |
+----------------------+
1 row in set (0.00 sec)

mysql> select json_length('[1, 2, {"a": 3}]');
+---------------------------------+
| json_length('[1, 2, {"a": 3}]') |
+---------------------------------+
|                               3 |
+---------------------------------+
1 row in set (0.00 sec)

mysql> select json_length('{"a": 1, "b": {"c": 30}}');
+-----------------------------------------+
| json_length('{"a": 1, "b": {"c": 30}}') |
+-----------------------------------------+
|                                       2 |
+-----------------------------------------+
1 row in set (0.00 sec)

mysql> select json_length('{"a": 1, "b": {"c": 30}}', '$.a');
+------------------------------------------------+
| json_length('{"a": 1, "b": {"c": 30}}', '$.a') |
+------------------------------------------------+
|                                              1 |
+------------------------------------------------+
1 row in set (0.00 sec)

JSON_TYPE(json_val)

回傳 JSON 值的型別,

mysql> select json_type('123');
+------------------+
| json_type('123') |
+------------------+
| INTEGER          |
+------------------+
1 row in set (0.00 sec)

mysql> select json_type('"abc"');
+--------------------+
| json_type('"abc"') |
+--------------------+
| STRING             |
+--------------------+
1 row in set (0.00 sec)

mysql> select json_type(cast(now() as json));
+--------------------------------+
| json_type(cast(now() as json)) |
+--------------------------------+
| DATETIME                       |
+--------------------------------+
1 row in set (0.00 sec)

mysql> select json_type(json_extract('{"a": [10, true]}', '$.a'));
+-----------------------------------------------------+
| json_type(json_extract('{"a": [10, true]}', '$.a')) |
+-----------------------------------------------------+
| ARRAY                                               |
+-----------------------------------------------------+
1 row in set (0.00 sec)

JSON_VALID(val)

判斷給定值是否是有效的 JSON 檔案,

mysql> select json_valid('hello'), json_valid('"hello"');
+---------------------+-----------------------+
| json_valid('hello') | json_valid('"hello"') |
+---------------------+-----------------------+
|                   0 |                     1 |
+---------------------+-----------------------+
1 row in set (0.00 sec)

JSON_TABLE(expr, path COLUMNS (column_list) [AS] alias)

從 JSON 檔案中提取資料并以表格的形式回傳,

該函式的完整語法如下:

JSON_TABLE(
    expr,
    path COLUMNS (column_list)
)   [AS] alias

column_list:
    column[, column][, ...]

column:
    name FOR ORDINALITY
    |  name type PATH string_path [on_empty] [on_error]
    |  name type EXISTS PATH string_path
    |  NESTED [PATH] path COLUMNS (column_list)

on_empty:
    {NULL | DEFAULT json_string | ERROR} ON EMPTY

on_error:
    {NULL | DEFAULT json_string | ERROR} ON ERROR

其中,

  • expr:可以回傳 JSON 檔案的運算式,可以是一個標量( JSON 檔案 ),列名或者一個函式呼叫( JSON_EXTRACT(t1.json_data,'$.post.comments') ),
  • path:JSON 的路徑運算式,
  • column:列的型別,支持以下四種型別:
    • name FOR ORDINALITY:序號,name 是列名,
    • name type PATH string_path [on_empty] [on_error]:提取指定路徑( string_path )的元素,name 是列名,type 是 MySQL 中的資料型別,
    • name type EXISTS PATH string_path:指定路徑( string_path )的元素是否存在,
    • NESTED [PATH] path COLUMNS (column_list):將嵌套物件或陣列與來自父物件或陣列的 JSON 值扁平化為一行輸出,
select *
 from
   json_table(
     '[{"x":2, "y":"8", "z":9, "b":[1,2,3]}, {"x":"3", "y":"7"}, {"x":"4", "y":6, "z":10}]',
     "$[*]" columns(
       id for ordinality,
       xval varchar(100) path "$.x",
       yval varchar(100) path "$.y",
       z_exist int exists path "$.z",
       nested path '$.b[*]' columns (b INT PATH '$')
     )
   ) as t;
+------+------+------+---------+------+
| id   | xval | yval | z_exist | b    |
+------+------+------+---------+------+
|    1 | 2    | 8    |       1 |    1 |
|    1 | 2    | 8    |       1 |    2 |
|    1 | 2    | 8    |       1 |    3 |
|    2 | 3    | 7    |       0 | NULL |
|    3 | 4    | 6    |       1 | NULL |
+------+------+------+---------+------+
5 rows in set (0.00 sec)

JSON_SCHEMA_VALID(schema,document)

判斷 document ( JSON 檔案 )是否滿足 schema ( JSON 物件)定義的規范要求,完整的規范要求可參考 Draft 4 of the JSON Schema specification ,如果不滿足,可通過 JSON_SCHEMA_VALIDATION_REPORT() 獲取具體的原因,

以下面這個 schema 為例,

set @schema = '{
   "type": "object",
   "properties": {
     "latitude": {
       "type": "number",
       "minimum": -90,
       "maximum": 90
     },
     "longitude": {
       "type": "number",
       "minimum": -180,
       "maximum": 180
     }
   },
   "required": ["latitude", "longitude"]
}';

它的要求如下:

  1. document 必須是 JSON 物件,
  2. JSON 物件必需的兩個屬性是 latitude 和 longitude,
  3. latitude 和 longitude 必須是數值型別,且兩者的大小分別在 -90 ~ 90,-180 ~ 180 之間,

下面通過具體的 document 來測驗一下,

mysql> set @document = '{"latitude": 63.444697,"longitude": 10.445118}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_schema_valid(@schema, @document);
+---------------------------------------+
| json_schema_valid(@schema, @document) |
+---------------------------------------+
|                                     1 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> set @document = '{"latitude": 63.444697}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_schema_valid(@schema, @document);
+---------------------------------------+
| json_schema_valid(@schema, @document) |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> select json_pretty(json_schema_validation_report(@schema, @document))\G
*************************** 1. row ***************************
json_pretty(json_schema_validation_report(@schema, @document)): {
  "valid": false,
  "reason": "The JSON document location '#' failed requirement 'required' at JSON Schema location '#'",
  "schema-location": "#",
  "document-location": "#",
  "schema-failed-keyword": "required"
}
1 row in set (0.00 sec)

mysql> set @document = '{"latitude": 91,"longitude": 0}';
Query OK, 0 rows affected (0.00 sec)

mysql> select json_schema_valid(@schema, @document);
+---------------------------------------+
| json_schema_valid(@schema, @document) |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> select json_pretty(json_schema_validation_report(@schema, @document))\G
*************************** 1. row ***************************
json_pretty(json_schema_validation_report(@schema, @document)): {
  "valid": false,
  "reason": "The JSON document location '#/latitude' failed requirement 'maximum' at JSON Schema location '#/properties/latitude'",
  "schema-location": "#/properties/latitude",
  "document-location": "#/latitude",
  "schema-failed-keyword": "maximum"
}
1 row in set (0.00 sec)

 

八、總結

如果要使用 JSON 型別,推薦使用 MySQL 8.0,相比于 MySQL 5.7,Partial update 帶來的性能提升還是十分明顯的,

Partial update 在存盤引擎層是默認開啟的,binlog 中是否開啟取決于 binlog_row_value_options ,該引數默認為空,不會開啟 Partial update,建議設定為 PARTIAL_JSON,

注意使用 Partial update 的前提條件,

當我們使用 JSON_CONTAINS、MEMBER OF、JSON_OVERLAPS 進行陣列相關的操作時,可使用 MySQL 8.0.17 引入的多值索引來加快查詢,

 

九、參考資料

  1. JSON

  2. The JSON Data Type

  3. JSON Functions

  4. Upgrading JSON data stored in TEXT columns

  5. Indexing JSON documents via Virtual Columns

  6. Partial update of JSON values

  7. MySQL 8.0: InnoDB Introduces LOB Index For Faster Updates

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/469736.html

標籤:MySQL

上一篇:JavaWeb連接MySQL資料庫

下一篇:開源之夏 2022 重磅來襲,歡迎報名 RadonDB 社區專案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more