當前位置: 妍妍網 > 碼農

MySQL 5.7 JSON 數據型別使用總結

2024-02-22碼農

從MySQL5.7.8開始,MySQL支持原生的JSON數據型別。MySQL 支持RFC 7159定義的全部json 數據型別,具體的包含四種基本型別( strings , numbers , booleans , null )和兩種結構化型別( objects and arrays )。

將 JSON 格式的字串儲存在字串列中相比,該數據型別具有以下優勢:

  • 自動驗證儲存在 JSON列中的 JSON 文件。無效的文件會產生錯誤。

  • 最佳化的儲存格式。儲存在列中的 JSON 文件被轉換為允許快速讀取文件元素的內部格式。

  • 當讀取 JSON 值時,不需要從文本表示中解析該值,使伺服器能夠直接透過鍵或陣列索引尋找子物件或巢狀值,而無需讀取文件中它們之前或之後的所有值。

  • JSON型別的儲存結構

    MySQL為了提供對 json 物件的支持,提供了一套將 json 字串轉為結構化二進制物件的儲存方式。json會被轉為二進制的doc物件儲存於磁盤中(在處理JSON時MySQL使用的 utf8mb4 字元集, utf8mb4 utf8 ascii 的超集)。

    doc物件包含兩個部份,type和value部份。其中type占1字節,可以表示16種型別:大的和小的json object型別、大的和小的 json array型別、literal型別(true、false、null三個值)、number型別(int6、uint16、int32、uint32、int64、uint64、double型別、utf8mb4 string型別和custom data(mysql自訂型別)

    JSON數據型別意義

    其實,沒有JSON數據型別的支持,我們一樣可以透過 varchar 型別或者 text 等型別來保存這一格式的數據,但是,為什麽還要專門增加這一數據格式的支持呢?其中肯定有較varchar或者text來儲存此型別更優越的地方。

  • 保證了 JSON數據型別的強校驗 ,JSON數據列會自動校驗存入此列的內容是否符合JSON格式,非正常格式則報錯,而varchar型別和text等型別本身是不存在這種機制的。

  • MySQL同時提 供了一組操作JSON型別數據的內建函式

  • 更最佳化的儲存格式,儲存在JSON列中的JSON數據會被轉成內部特定的儲存格式, 允許快速讀取

  • 可以基於JSON格式的特征支持 修改特定的鍵值 。(即不需要把整條內容拿出來放到程式中遍歷然後尋找替換再塞回去,MySQL內建的函式允許你透過一條SQL語句就能搞定)

  • JSON 數據型別

    JSON 物件

  • 使用物件操作的方法進行查詢: 欄位->'$.json內容'

  • 使用函式進行查詢: json_extract(欄位, '$.json內容')

  • 獲取JSON陣列/物件長度: JSON_LENGTH()

  • JSON 陣列

  • 使用物件操作的方法進行查詢: 欄位->'$[0].內容'

  • 使用函式進行查詢: JSON_CONTAINS(欄位,JSON_OBJECT('json內容', '內容'))

  • 獲取JSON陣列/物件長度: JSON_LENGTH()

  • 建立 JSON

    類似varchar,設定主要將欄位的type是json,不能設定長度,可以是NULL但不能有預設值。

    CREATETABLE`tinywan_json` (
    idINT ( 11 ) NOTNULL auto_increment,
     tag jsonNOTNULLCOMMENT'標簽列表',
     catagory jsonNOTNULLCOMMENT'分類列表',
     create_time INT ( 11 ) DEFAULTNULLCOMMENT'建立時間',
    PRIMARY KEY ( id ) 
    ENGINE = INNODBDEFAULTcharset = utf8mb4;

    使用 describe tinywan_json 檢視建立的表結構:

    mysql> describe tinywan_json;
    +-------------+---------+------+-----+---------+----------------+
    | Field | Type | Null | Key | Default | Extra |
    +-------------+---------+------+-----+---------+----------------+
    | id | int(11) | NO | PRI | NULL | auto_increment |
    | tag | json | NO | | NULL | |
    | catagory | json | NO | | NULL | |
    | create_time | int(11) | YES | | NULL | |
    +-------------+---------+------+-----+---------+----------------+
    4 rows in set (0.05 sec)

    寫入JSON數據

    INSERTINTO tinywan_json ( catagory, tag )
    VALUES
     ( '{"0":"廚衛","1":"童裝","2":"休閑"}''["Good","Fine","Bad"]' );

    MYSQL也有專門的函式 JSON_OBJECT , JSON_ARRAY 函式生成json格式的數據。

    INSERTINTO tinywan_json ( catagory, tag )
    VALUES
     ( JSON_OBJECT ( "name""John""age"23 ), JSON_ARRAY ( 'Low''Middle''High' ) )

    檢視插入的兩條數據

    mysql> select * from tinywan_json;
    +----+---------------------------+-----------------------------------------+-------------+
    | id | tag | catagory | create_time |
    +----+---------------------------+-----------------------------------------+-------------+
    | 1 | ["Good""Fine""Bad"] | {"0""廚衛""1""童裝""2""休閑"} | NULL |
    | 2 | ["Low""Middle""High"] | {"age": 23, "name""John"} | NULL |
    +----+---------------------------+-----------------------------------------+-------------+
    2 rows inset (0.10 sec)

    查詢 JSON

    查詢json中的數據使用 column->path 的形式,其中物件型別 path 這樣表示 $.path ,而陣列型別則是 $[index]

    檢視每一行數據的JSON型別,篩選如下:

    mysql> select tag,json_type(tag),catagory,json_type(catagory) from tinywan_json;
    +---------------------------+----------------+-----------------------------------------+---------------------+
    | tag | json_type(tag) | catagory | json_type(catagory) |
    +---------------------------+----------------+-----------------------------------------+---------------------+
    | ["Good""Fine""Bad"] | ARRAY | {"0""廚衛""1""童裝""2""休閑"} | OBJECT |
    | ["Low""Middle""High"] | ARRAY | {"age"23"name""John"} | OBJECT |
    +---------------------------+----------------+-----------------------------------------+---------------------+
    2 rows inset (0.07 sec)

    篩選出 catagory name = John 的那條記錄的所有 tag:

    mysql> select tag from tinywan_json where catagory->'$.name' = 'John';
    +---------------------------+
    | tag |
    +---------------------------+
    | ["Low""Middle""High"] |
    +---------------------------+
    1 row inset (0.06 sec)

    透過 json_type 確定了 tag 的數據型別是 ARRAY ,那麽就可以使用陣列索引的方式查詢:

    mysql> select tag->'$[0]',tag->'$[2024]',tag->'$.notexist'from tinywan_json where catagory->'$.name' = 'John';
    +-------------+----------------+-------------------+
    | tag->'$[0]' | tag->'$[2024]' | tag->'$.notexist' |
    +-------------+----------------+-------------------+
    "Low" | NULL | NULL |
    +-------------+----------------+-------------------+
    1 row inset (0.05 sec)

    上面的例子中,可以看到,使用 tag->'$[0]' 的方式如期獲取了 tag 陣列內的第一個數據。另外兩個,一個 tag->'$[2024]' 是不存在的陣列元素, 一個tag->'$.notexist' 是不存在的物件鍵,都返回了 null ,這與預期的一致。

    還要一個小小的問題,返回的 tag->'$[0]' = "Low" ,並且左右有一對雙引號,可否去掉呢?使用 JSON_UNQUOTE 函式

    mysql> select JSON_UNQUOTE(tag->'$[0]'),tag->'$[2024]',tag->'$.notexist' from tinywan_json where catagory->'$.name' = 'John';
    +---------------------------+----------------+-------------------+
    | JSON_UNQUOTE(tag->'$[0]') | tag->'$[2024]' | tag->'$.notexist' |
    +---------------------------+----------------+-------------------+
    | Low | NULL | NULL |
    +---------------------------+----------------+-------------------+
    1 row inset (0.06 sec)

    JSON 條件查詢

    因為JSON不同於字串,所以如果用字串和JSON欄位進行比較,是不會相等的。這時可以使用 CAST 函式,將字串轉成 JSON 的形式。

    mysql> select * from tinywan_json where catagory = '{"age": 23, "name": "John"}';
    Empty set
    mysql> select * from tinywan_json where catagory = CAST('{"age": 23, "name": "John"}'asJSON);
    +----+---------------------------+-----------------------------+-------------+
    | id | tag | catagory | create_time |
    +----+---------------------------+-----------------------------+-------------+
    | 2 | ["Low""Middle""High"] | {"age"23"name""John"} | NULL |
    +----+---------------------------+-----------------------------+-------------+
    1 row inset (0.07 sec)

    對應地, select 欄位和 where 篩選條件中,均可以使用 column->path 的方式操作。

    mysql> select tag,catagory->'$.age'from tinywan_json where tag->'$[1]' = 'Middle';
    +---------------------------+-------------------+
    | tag | catagory->'$.age' |
    +---------------------------+-------------------+
    | ["Low""Middle""High"] | 23 |
    +---------------------------+-------------------+
    1 row inset (0.07 sec)

    特別註意的是 。JSON中的元素是嚴格區分變量型別的,比如說整型和字串是嚴格區分的。

    mysql> select * from tinywan_json where catagory->'$.age' = 23;
    +----+---------------------------+-----------------------------+-------------+
    | id | tag | catagory | create_time |
    +----+---------------------------+-----------------------------+-------------+
    | 2 | ["Low""Middle""High"] | {"age": 23, "name""John"} | NULL |
    +----+---------------------------+-----------------------------+-------------+
    1 row inset (0.06 sec)
    mysql> select * from tinywan_json where catagory->'$.age' = '23';
    Empty set

    除了用 column->path 的形式搜尋,還可以用 JSON_CONTAINS 函式,但和 column->path 的形式有點相反的是, JSON_CONTAINS 第二個參數是不接受整數的,無論 json 元素是整型還是字串,否則會出現錯誤 nvalid data type for JSON data in argument 2 to function json_contains; a JSON string or JSON type is required.

    mysql> select * from tinywan_json where JSON_CONTAINS(catagory,'23','$.age');
    +----+---------------------------+-----------------------------+-------------+
    | id | tag | catagory | create_time |
    +----+---------------------------+-----------------------------+-------------+
    | 2 | ["Low""Middle""High"] | {"age"23"name""John"} | NULL |
    +----+---------------------------+-----------------------------+-------------+
    1 row inset (0.08 sec)
    mysql> select * from tinywan_json where JSON_CONTAINS(catagory,23,'$.age');
    Invalid data typeforJSON data in argument 2 to functionjson_contains; a JSONstring or JSONtype is required.

    上面打印的第一行,才是正確的寫法。整數應該寫成 'int' ,字串則要看值內的情況,很多情況下需要帶上雙引號, '"string"' ,這樣寫。如下:

    mysql> select * from tinywan_json where JSON_CONTAINS(tag,'"Fine"');
    +----+-------------------------+-----------------------------------------+-------------+
    | id | tag | catagory | create_time |
    +----+-------------------------+-----------------------------------------+-------------+
    | 1 | ["Good""Fine""Bad"] | {"0""廚衛""1""童裝""2""休閑"} | NULL |
    +----+-------------------------+-----------------------------------------+-------------+
    1 row inset (0.07 sec)

    小結

  • JSON型別無須預定義欄位,適合拓展資訊的儲存

  • 單個JSON文件的大小不能超過 4G ;單個 KEY 的大小不能超過兩個字節,即 64K

  • JSON型別適合套用於不常更新的靜態數據

  • 對搜尋較頻繁的數據建議增加虛擬列並建立索引