1. 一文說透 MySQL JSON 數(shù)據(jù)類型(收藏)

        共 49334字,需瀏覽 99分鐘

         ·

        2022-05-16 14:27

        JSON 數(shù)據(jù)類型是 MySQL 5.7.8 開始支持的。在此之前,只能通過字符類型(CHAR,VARCHAR 或 TEXT )來保存 JSON 文檔。

        相對(duì)字符類型,原生的 JSON 類型具有以下優(yōu)勢(shì):

        1. 在插入時(shí)能自動(dòng)校驗(yàn)文檔是否滿足 JSON 格式的要求。
        2. 優(yōu)化了存儲(chǔ)格式。無需讀取整個(gè)文檔就能快速訪問某個(gè)元素的值。

        在 JSON 類型引入之前,如果我們想要獲取 JSON 文檔中的某個(gè)元素,必須首先讀取整個(gè) JSON 文檔,然后在客戶端將其轉(zhuǎn)換為 JSON 對(duì)象,最后再通過對(duì)象獲取指定元素的值。

        下面是 Python 中的獲取方式。

        import?json

        #?JSON?字符串:
        x?=??'{?"name":"John",?"age":30,?"city":"New?York"}'

        #?將?JSON?字符串轉(zhuǎn)換為?JSON?對(duì)象:
        y?=?json.loads(x)

        #?讀取?JSON?對(duì)象中指定元素的值:
        print(y["age"])

        這種方式有兩個(gè)弊端:一、消耗磁盤 IO,二、消耗網(wǎng)絡(luò)帶寬,如果 JSON 文檔比較大,在高并發(fā)場(chǎng)景,有可能會(huì)打爆網(wǎng)卡。

        如果使用的是 JSON 類型,相同的需求,直接使用 SQL 命令就可搞定。不僅能節(jié)省網(wǎng)絡(luò)帶寬,結(jié)合后面提到的函數(shù)索引,還能降低磁盤 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)

        本文將從以下幾個(gè)方面展開:

        1. 什么是 JSON。
        2. JSON 字段的增刪改查操作。
        3. 如何對(duì) JSON 字段創(chuàng)建索引。
        4. 如何將存儲(chǔ) JSON 字符串的字符字段升級(jí)為 JSON 字段。
        5. 使用 JSON 時(shí)的注意事項(xiàng)。
        6. Partial Updates。
        7. 其它 JSON 函數(shù)。

        一、什么是 JSON

        JSON 是 JavaScript Object Notation(JavaScript 對(duì)象表示法)的縮寫,是一個(gè)輕量級(jí)的,基于文本的,跨語言的數(shù)據(jù)交換格式。易于閱讀和編寫。

        JSON 的基本數(shù)據(jù)類型如下:

        • 數(shù)值:十進(jìn)制數(shù),不能有前導(dǎo) 0,可以為負(fù)數(shù)或小數(shù),還可以為 e 或 E 表示的指數(shù)。

        • 字符串:字符串必須用雙引號(hào)括起來。

        • 布爾值:true,false。

        • 數(shù)組:一個(gè)由零或多個(gè)值組成的有序序列。每個(gè)值可以為任意類型。數(shù)組使用方括號(hào)[] 括起來,元素之間用逗號(hào),分隔。譬如,

          [1,?"abc",?null,?true,?"10:27:06.000000",?{"id":?1}]
        • 對(duì)象:一個(gè)由零或者多個(gè)鍵值對(duì)組成的無序集合。其中鍵必須是字符串,值可以為任意類型。

          對(duì)象使用花括號(hào){}括起來,鍵值對(duì)之間使用逗號(hào),分隔,鍵與值之間用冒號(hào):分隔。譬如,

          {"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)

        也可使用函數(shù),常用的有 JSON_ARRAY() 和 JSON_OBJECT(),前者用于構(gòu)造 JSON 數(shù)組,后者用于構(gòu)造 JSON 對(duì)象。如,

        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)

        對(duì)于 JSON 文檔,KEY 名不能重復(fù)。

        如果插入的值中存在重復(fù) KEY,在 MySQL 8.0.3 之前,遵循 first duplicate key wins 原則,會(huì)保留第一個(gè) KEY,后面的將被丟棄掉。

        從 MySQL 8.0.3 開始,遵循的是 last duplicate key wins 原則,只會(huì)保留最后一個(gè) KEY。

        下面通過一個(gè)具體的示例來看看兩者的區(qū)別。

        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 是路徑。該函數(shù)會(huì)從 JSON 文檔提取指定路徑(path)的元素。如果指定 path 不存在,會(huì)返回 NULL。可指定多個(gè) path,匹配到的多個(gè)值會(huì)以數(shù)組形式返回。

        下面我們結(jié)合一些具體的示例來看看 path 及 JSON_EXTRACT 的用法。

        首先我們看看數(shù)組。

        數(shù)組的路徑是通過下標(biāo)來表示的。第一個(gè)元素的下標(biāo)是 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] 獲取數(shù)組的子集。

        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?代表最后一個(gè)元素的下標(biāo)
        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)

        也可通過 [*] 獲取數(shù)組中的所有元素。

        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)

        接下來,我們看看對(duì)象。

        對(duì)象的路徑是通過 KEY 來表示的。

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

        #?如果 KEY 在路徑表達(dá)式中不合法(譬如存在空格),則在引用這個(gè) KEY 時(shí),需用雙引號(hào)括起來。
        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)

        除此之外,還可通過 .* 獲取對(duì)象中的所有元素。

        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,都是語法糖,在實(shí)際使用的時(shí)候都會(huì)轉(zhuǎn)化為 JSON_EXTRACT。

        column->path 等同于 JSON_EXTRACT(column, path) ,只能指定一個(gè)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 類似,只不過其返回的是字符串。以下三者是等價(jià)的。

        • 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] ...)

        插入新值。

        僅當(dāng)指定位置或指定 KEY 的值不存在時(shí),才執(zhí)行插入操作。另外,如果指定的 path 是數(shù)組下標(biāo),且 json_doc 不是數(shù)組,該函數(shù)首先會(huì)將 json_doc 轉(zhuǎn)化為數(shù)組,然后再插入新值。

        下面我們看幾個(gè)示例。

        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] ...)

        插入新值,并替換已經(jīng)存在的值。

        換言之,如果指定位置或指定 KEY 的值不存在,會(huì)執(zhí)行插入操作,如果存在,則執(zhí)行更新操作。

        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] ...)

        替換已經(jīng)存在的值。

        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)

        最后一個(gè)查詢,雖然兩個(gè) path 都是 '$[1]' ,但作用對(duì)象不一樣,第一個(gè) path 的作用對(duì)象是 '["a", ["b", "c"], "d", "e"]' ,第二個(gè) path 的作用對(duì)象是刪除了 '$[1]' 后的數(shù)組,即 '["a", "d", "e"]' 。

        三、如何對(duì) JSON 字段創(chuàng)建索引

        同 TEXT,BLOB 字段一樣,JSON 字段不允許直接創(chuàng)建索引。

        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.

        即使支持,實(shí)際意義也不大,因?yàn)槲覀円话闶腔谖臋n中的元素進(jìn)行查詢,很少會(huì)基于整個(gè) ?JSON 文檔。

        對(duì)文檔中的元素進(jìn)行查詢,就需要用到 MySQL 5.7 引入的虛擬列及函數(shù)索引。

        下面我們來看一個(gè)具體的示例。

        #?C2?即虛擬列
        # index (c2)?對(duì)虛擬列添加索引。
        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)

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

        注意,在創(chuàng)建虛擬列時(shí)需指定 ?JSON_UNQUOTE,將 c1 -> "$.name" 的返回值轉(zhuǎn)換為字符串。

        四、如何將存儲(chǔ) JSON 字符串的字符字段升級(jí)為 JSON 字段

        在 MySQL 支持 JSON 類型之前,對(duì)于 JSON 文檔,一般是以字符串的形式存儲(chǔ)在字符類型(VARCHAR 或 TEXT)中。

        在 JSON 類型出來之后,如何將這些字符字段升級(jí)為 JSON 字段呢?

        為方便演示,這里首先構(gòu)建測(cè)試數(shù)據(jù)。

        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"}');

        注意,最后一個(gè)文檔有問題,不是合格的 JSON 文檔。

        如果使用 DDL 直接修改字段的數(shù)據(jù)類型,會(huì)報(bào)錯(cuò)。

        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'.

        下面,我們看看具體的升級(jí)步驟。

        (1)使用 json_valid 函數(shù)找出不滿足 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 時(shí)的注意事項(xiàng)

        對(duì)于 JSON 類型,有以下幾點(diǎn)需要注意:

        1. 在 MySQL 8.0.13 之前,不允許對(duì) BLOB,TEXT,GEOMETRY,JSON 字段設(shè)置默認(rèn)值。從 MySQL 8.0.13 開始,取消了這個(gè)限制。

          設(shè)置時(shí),注意默認(rèn)值需通過小括號(hào)()括起來,否則的話,還是會(huì)提示 JSON 字段不允許設(shè)置默認(rèn)值。

          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. 不允許直接創(chuàng)建索引,可創(chuàng)建函數(shù)索引。

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

        4. 插入時(shí),單個(gè)文檔的大小受到 max_allowed_packet 的限制,該參數(shù)最大是 1G。

        六、Partial Updates

        在 MySQL 5.7 中,對(duì) JSON 文檔進(jìn)行更新,其處理策略是,刪除舊的文檔,再插入新的文檔。即使這個(gè)修改很微小,只涉及幾個(gè)字節(jié),也會(huì)替換掉整個(gè)文檔。很顯然,這種處理方式的效率較為低下。

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

        下面我們具體來看看。

        6.1 使用 Partial Updates 的條件

        為方便闡述,這里先構(gòu)造測(cè)試數(shù)據(jù)。

        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 進(jìn)行 UPDATE 操作,如,

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

          不使用這三個(gè)函數(shù),而顯式賦值,就不會(huì)進(jìn)行部分更新,如,

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

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

          否則的話,就不會(huì)進(jìn)行部分更新,如,

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

        關(guān)于最后一個(gè)條件,我們看看下面這個(gè)示例。

        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)

        示例中,用到了兩個(gè)函數(shù):JSON_STORAGE_SIZE 和 JSON_STORAGE_FREE ,前者用來獲取 JSON 文檔的空間使用情況,后者用來獲取 JSON 文檔在執(zhí)行原地更新后的空間釋放情況。

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

        因?yàn)榈谝淮蔚?JSON_SET 復(fù)用了 JSON_REMOVE 釋放的空間。而第二次的 JSON_SET 執(zhí)行的是更新操作,且 'mysql' 比 3306 需要更多的存儲(chǔ)空間。


        6.2 如何在 binlog 中開啟 Partial Updates

        Partial Updates 不僅僅適用于存儲(chǔ)引擎層,還可用于主從復(fù)制場(chǎng)景。

        主從復(fù)制開啟 Partial Updates,只需將參數(shù) binlog_row_value_options(默認(rèn)為空)設(shè)置為 PARTIAL_JSON。

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

        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)

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

        在將 binlog_row_value_options 設(shè)置為 PARTIAL_JSON 后,對(duì)于可使用 Partial Updates 的操作,在 binlog 中,不再通過 ROWS_EVENT 來記錄,而是新增了一個(gè) PARTIAL_UPDATE_ROWS_EVENT 的事件類型。

        需要注意的是,binlog 中使用 Partial Updates,只需滿足存儲(chǔ)引擎層使用 Partial Updates 的前三個(gè)條件,無需考慮變更前后,JSON 文檔的空間使用是否會(huì)增加。

        6.3 關(guān)于 Partial Updates 的性能測(cè)試

        首先構(gòu)造測(cè)試數(shù)據(jù),t 表一共有 16 個(gè)文檔,每個(gè)文檔近 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;

        接下來,測(cè)試下述 SQL

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

        在以下四種場(chǎng)景下的執(zhí)行時(shí)間:

        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

        分別執(zhí)行 10 次,去掉最大值和最小值后求平均值。

        最后的測(cè)試結(jié)果如下:

        以 MySQL 5.7.36 的查詢時(shí)間作為基準(zhǔn):

        1. MySQL 8.0 只開啟存儲(chǔ)引擎層的 Partial Updates,查詢時(shí)間比 MySQL 5.7 快 1.94 倍。
        2. MySQL 8.0 同時(shí)開啟存儲(chǔ)引擎層和 binlog 中的 Partial Updates,查詢時(shí)間比 MySQL 5.7 快 4.87 倍。
        3. 如果在 2 的基礎(chǔ)上,同時(shí)將 binlog_row_image 設(shè)置為 MINIMAL,查詢時(shí)間更是比 MySQL 5.7 快 102.22 倍。

        當(dāng)然,在生產(chǎn)環(huán)境,我們一般很少將 binlog_row_image 設(shè)置為 MINIMAL。

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

        七、其它 JSON 函數(shù)

        7.1 查詢相關(guān)

        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。

        函數(shù)中的 one_or_all 可指定 one 或 all,one 是任意一個(gè)路徑存在就返回 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] ...])

        返回某個(gè)字符串(search_str)在 JSON 文檔中的位置,其中,

        • one_or_all:匹配的次數(shù),one 是只匹配一次,all 是匹配所有。如果匹配到多個(gè),結(jié)果會(huì)以數(shù)組的形式返回。
        • search_str:子串,支持模糊匹配:% _ 。
        • escape_char:轉(zhuǎn)義符,如果該參數(shù)不填或?yàn)?NULL,則取默認(rèn)轉(zhuǎn)義符\。
        • 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 對(duì)應(yīng)元素最外層的 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)的元素。

        該函數(shù)的完整語法如下:

        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:返回值的類型,不指定,則默認(rèn)是 VARCHAR(512)。不指定字符集,則默認(rèn)是 utf8mb4,且區(qū)分大小寫。
        • on_empty:如果指定路徑?jīng)]有值,會(huì)觸發(fā) on_empty 子句, 默認(rèn)是返回 NULL,也可指定 ERROR 拋出錯(cuò)誤,或者通過 DEFAULT value 返回默認(rèn)值。
        • on_error:三種情況下會(huì)觸發(fā) on_error 子句:從數(shù)組或?qū)ο笾刑崛≡貢r(shí),會(huì)解析到多個(gè)值;類型轉(zhuǎn)換錯(cuò)誤,譬如將 "abc" 轉(zhuǎn)換為 unsigned 類型;值被 truncate 了。默認(rèn)是返回 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 數(shù)組的一個(gè)元素,如果是,則返回 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 引入的,用來比較兩個(gè) JSON 文檔是否有相同的鍵值對(duì)或數(shù)組元素,如果有,則返回 1,否則是 0。如果兩個(gè)參數(shù)都是標(biāo)量,則判斷這兩個(gè)標(biāo)量是否相等。

        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 數(shù)組中。當(dāng)我們使用 JSON_CONTAINS、MEMBER OF、JSON_OVERLAPS 進(jìn)行數(shù)組相關(guān)的操作時(shí),可使用多值索引來加快查詢。


        7.2 修改相關(guān)

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

        向數(shù)組指定位置追加元素。如果指定 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] ...)

        向數(shù)組指定位置插入元素。

        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 引入的,用來合并多個(gè) JSON 文檔。其合并規(guī)則如下:

        1. 如果兩個(gè)文檔不全是 JSON 對(duì)象,則合并后的結(jié)果是第二個(gè)文檔。
        2. 如果兩個(gè)文檔都是 JSON 對(duì)象,且不存在著同名 KEY,則合并后的文檔包括兩個(gè)文檔的所有元素,如果存在著同名 KEY,則第二個(gè)文檔的值會(huì)覆蓋第一個(gè)。
        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)

        #?如果第二個(gè)文檔存在 null 值,文檔合并后不會(huì)輸出對(duì)應(yīng)的 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。也是用來合并文檔,但合并規(guī)則與 JSON_MERGE_PATCH 有所不同。

        1. 兩個(gè)文檔中,只要有一個(gè)文檔是數(shù)組,則另外一個(gè)文檔會(huì)合并到該數(shù)組中。
        2. 兩個(gè)文檔都是 JSON 對(duì)象,若存在著同名 KEY ,第二個(gè)文檔并不會(huì)覆蓋第一個(gè),而是會(huì)將值 append 到第一個(gè)文檔中。
        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 開始不建議使用,后續(xù)會(huì)移除。


        7.3 其它輔助函數(shù)

        JSON_QUOTE(string)

        生成有效的 JSON 字符串,主要是對(duì)一些特殊字符(如雙引號(hào))進(jìn)行轉(zhuǎn)義。

        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) 進(jìn)行類型轉(zhuǎn)換。


        JSON_UNQUOTE(json_val)

        將 JSON 轉(zhuǎn)義成字符串輸出。

        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 字符串會(huì)用雙引號(hào)引起來,加了 JSON_UNQUOTE 就沒有。但本質(zhì)上,前者是 JSON 中的 STRING 類型,后者是 MySQL 中的字符類型,這一點(diǎn)可通過 JSON_VALID 來判斷。


        JSON_OBJECTAGG(key, value)

        取表中的兩列作為參數(shù),其中,第一列是 key,第二列是 value,返回 JSON 對(duì)象。如,

        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 數(shù)組,注意,JSON 數(shù)組中元素的順序是隨機(jī)的。

        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 有關(guān),用于計(jì)算 JSON 文檔在進(jìn)行部分更新后的剩余空間。


        JSON_STORAGE_SIZE(json_val)

        MySQL 5.7.22 引入的,用于計(jì)算 JSON 文檔的空間使用情況。


        JSON_DEPTH(json_doc)

        返回 JSON 文檔的最大深度。對(duì)于空數(shù)組,空對(duì)象,標(biāo)量值,其深度為 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 文檔的長(zhǎng)度,其計(jì)算規(guī)則如下:

        1. 如果是標(biāo)量值,其長(zhǎng)度為 1。
        2. 如果是數(shù)組,其長(zhǎng)度為數(shù)組元素的個(gè)數(shù)。
        3. 如果是對(duì)象,其長(zhǎng)度為對(duì)象元素的個(gè)數(shù)。
        4. 不包括嵌套數(shù)據(jù)和嵌套對(duì)象的長(zhǎng)度。
        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 文檔中提取數(shù)據(jù)并以表格的形式返回。

        該函數(shù)的完整語法如下:

        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 文檔的表達(dá)式??梢允且粋€(gè)標(biāo)量( JSON 文檔 ),列名或者一個(gè)函數(shù)調(diào)用( JSON_EXTRACT(t1.json_data,'$.post.comments') )。
        • path:JSON 的路徑表達(dá)式,
        • column:列的類型,支持以下四種類型:
          • name FOR ORDINALITY:序號(hào)。name 是列名。
          • name type PATH string_path [on_empty] [on_error]:提取指定路徑( string_path )的元素。name 是列名,type 是 MySQL 中的數(shù)據(jù)類型。
          • name type EXISTS PATH string_path:指定路徑( string_path )的元素是否存在。
          • NESTED [PATH] path COLUMNS (column_list):將嵌套對(duì)象或數(shù)組與來自父對(duì)象或數(shù)組的 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 對(duì)象)定義的規(guī)范要求。完整的規(guī)范要求可參考 Draft 4 of the JSON Schema specification (https://json-schema.org/specification-links.html#draft-4)。如果不滿足,可通過 JSON_SCHEMA_VALIDATION_REPORT() 獲取具體的原因。

        以下面這個(gè) 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 對(duì)象。
        2. JSON 對(duì)象必需的兩個(gè)屬性是 latitude 和 longitude。
        3. latitude 和 longitude 必須是數(shù)值類型,且兩者的大小分別在 -90 ~ 90,-180 ~ 180 之間。

        下面通過具體的 document 來測(cè)試一下。

        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)

        八、總結(jié)

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

        Partial update 在存儲(chǔ)引擎層是默認(rèn)開啟的,binlog 中是否開啟取決于 binlog_row_value_options 。該參數(shù)默認(rèn)為空,不會(huì)開啟 Partial update,建議設(shè)置為 PARTIAL_JSON。

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

        當(dāng)我們使用 JSON_CONTAINS、MEMBER OF、JSON_OVERLAPS 進(jìn)行數(shù)組相關(guān)的操作時(shí),可使用 MySQL 8.0.17 引入的多值索引來加快查詢。

        九、參考資料

        1. JSON:https://zh.wikipedia.org/wiki/JSON

        2. The JSON Data Type:https://dev.mysql.com/doc/refman/8.0/en/json.html

        3. JSON Functions:https://dev.mysql.com/doc/refman/8.0/en/json-functions.html

        4. Upgrading JSON data stored in TEXT columns:https://dev.mysql.com/blog-archive/upgrading-json-data-stored-in-text-columns/

        5. Indexing JSON documents via Virtual Columns:https://dev.mysql.com/blog-archive/indexing-json-documents-via-virtual-columns/

        6. Partial update of JSON values:https://dev.mysql.com/blog-archive/partial-update-of-json-values/

        7. MySQL 8.0: InnoDB Introduces LOB Index For Faster Updates:https://dev.mysql.com/blog-archive/mysql-8-0-innodb-introduces-lob-index-for-faster-updates/

        瀏覽 28
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 色婷婷婷婷色 | 围产精品久久 | 蜜桃91麻豆精品一二三区 | sandrarusso精品艳妇 | 2017亚洲天堂 |