mysql的json特性的應用

概述

說實話,我的不是很喜歡這種特性,關係型數據庫就應該有關係型數據庫的樣子,而不是爲了留住用戶而強加給他一些nosql的特性,並且尚未人家作的好,這樣反而會形成一種四不像的感受。sql和nosql是互補的,不是競爭關係。html

那既然碰到了就學習下吧,咱們從使用者的角度來看,json的特性是否能完成之前結構化數據的操做以及不足之處mysql

函數

以下,是官方文檔中所支持的JSON操做sql

Namemongodb

Description數據庫

JSON_APPEND() (deprecated 5.7.9)json

Append data to JSON document數組

JSON_ARRAY()mybatis

Create JSON arrayapp

JSON_ARRAY_APPEND()框架

Append data to JSON document

JSON_ARRAY_INSERT()

Insert into JSON array

->

Return value from JSON column after evaluating path; equivalent to JSON_EXTRACT().

JSON_CONTAINS()

Whether JSON document contains specific object at path

JSON_CONTAINS_PATH()

Whether JSON document contains any data at path

JSON_DEPTH()

Maximum depth of JSON document

JSON_EXTRACT()

Return data from JSON document

->>

Return value from JSON column after evaluating path and unquoting the result; equivalent to JSON_UNQUOTE(JSON_EXTRACT()).

JSON_INSERT()

Insert data into JSON document

JSON_KEYS()

Array of keys from JSON document

JSON_LENGTH()

Number of elements in JSON document

JSON_MERGE() (deprecated 5.7.22)

Merge JSON documents, preserving duplicate keys. Deprecated synonym for JSON_MERGE_PRESERVE()

JSON_MERGE_PATCH()

Merge JSON documents, replacing values of duplicate keys

JSON_MERGE_PRESERVE()

Merge JSON documents, preserving duplicate keys

JSON_OBJECT()

Create JSON object

JSON_PRETTY()

Prints a JSON document in human-readable format, with each array element or object member printed on a new line, indented two spaces with respect to its parent.

JSON_QUOTE()

Quote JSON document

JSON_REMOVE()

Remove data from JSON document

JSON_REPLACE()

Replace values in JSON document

JSON_SEARCH()

Path to value within JSON document

JSON_SET()

Insert data into JSON document

JSON_STORAGE_SIZE()

Space used for storage of binary representation of a JSON document; for a JSON column, the space used when the document was inserted, prior to any partial updates

JSON_TYPE()

Type of JSON value

JSON_UNQUOTE()

Unquote JSON value

JSON_VALID()

Whether JSON value is valid

這裏還須要加上一個下面的函數,功能是吧json的字符串轉成對象,不然會真的當成字符串插入到mysql中,下面會碰到這個問題

CAST('{"a":"b"}' AS JSON

準備

建立表

CREATE TABLE `user` (
`uid` INT(11) NOT NULL AUTO_INCREMENT,
`info` JSON NULL DEFAULT NULL,
`a` VARCHAR(50) NULL DEFAULT NULL,
`b` VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (`uid`),
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB

如上,info字段是JSON類型

新增

insert into user(info) values('{"mail": "abc", "name": "tomab", "address": "e"}');

和普通的插入操做同樣,只不過值是符合json規範的字符串,不規範插不進去

查找

SELECT uid,json_extract(info,'$.mail') AS 'mail',json_extract(info,'$.name') AS 'name' FROM USER;

這樣查找出來的數據會帶了雙引號「」,很不爽,咱們使用JSON_UNQUOTE函數處理下

SELECT uid,JSON_UNQUOTE(json_extract(info,'$.mail')) AS 'mail',json_extract(info,'$.name') AS 'name' FROM USER;

這樣mail字段就沒有雙引號了

你們也能看出來,這種取數據的方式,充斥了大量的JSON_UNQUOTE,json_extract和美圓符號,相比結構化,sql數據長了很多,並且必需要as成pojo裏對應的屬性,不然你的屬性名稱會變成相似json_extract(info,'$.name'),這樣ORM框架就不能正確的設值了

mysql也意識到了,因此提供了->和->>符合來代替,代碼稍微簡潔點,可是as依然必不可少

SELECT uid,info->'$.address',info->'$.name' AS 'name' FROM USER;
SELECT uid,info->>'$.address',info->>'$.name' AS 'name' FROM USER;

模糊查找和排序

SELECT * FROM user where info->'$.address' = 'e' and info->'$.name' like '%o%' order by info->'$.name'

確實都支持,可是沒有索引,數據量大時,效率不會很高

好在mysql提供了虛擬列的功能,能夠把json的屬性創建成虛擬列,而後在該列上加索引便可,這裏強調一下,虛擬列和json的特性不要緊,只是恰好能用上而已,並且虛擬列在json特性以前就有了

ALTER TABLE user ADD user_name varchar(128) GENERATED ALWAYS AS (json_extract(info,'$.name')) VIRTUAL;
ALTER TABLE user ADD INDEX index_name (a,b,user_name)

該列是能夠經過desc user來看到的,而後加上索引便可,而且該列是能夠和表的普通列創建聯合索引的

MySQL 5.7中,支持兩種Generated Column,即Virtual Generated Column和Stored Generated Column,前者只將Generated Column保存在數據字典中(表的元數據),並不會將這一列數據持久化到磁盤上;後者會將Generated Column持久化到磁盤上,而不是每次讀取的時候計算所得。很明顯,後者存放了能夠經過已有數據計算而得的數據,須要更多的磁盤空間

能夠看到,不管哪一種虛擬列,都增長了額外的操做或者空間,效率比結構化的數據要低

修改

覆蓋

update user set info = '{"mail": "abc", "name": "tomab", "address": "e"}';

這種修改,直接把內容當作一個字符串覆蓋,簡單粗暴

部分修改

這種操做首先必需要徹底瞭解存儲的json的格式,把他當作一個json對象而不是字符串

修改對象可使用JSON_INSERT()JSON_REPLACE()JSON_SET()JSON_MERGE_PATCH()JSON_MERGE_PRESERVE()

json_replace:只替換已經存在的舊值

json_set:替換舊值,並插入不存在的新值,這是最經常使用的

json_insert:插入新值,但不替換已經存在的舊值

JSON_MERGE_PATCH():有相同的屬性會覆蓋前面的,例子見https://dev.mysql.com/doc/refman/5.7/en/json-modification-functions.html#function_json-merge-patch

JSON_MERGE_PRESERVE():有相同的屬性不會覆蓋,會變成一個數組

修改對象

好比有這樣一個需求,想把mail的值變成一個對象,即相似以下:

{
"mail": {
"a": "b"
},
"name": "David",
"address": "Shangahai"
}

屁顛的使用

update user set info=JSON_SET(info,'$.mail','{"a":"b"}') where uid=5;

發現,mysql把參數當作字符串來處理了,結果以下:

{
"mail": "{\"a\":\"b\"}",
"name": "co",
"address": "e"
}

不是咱們想要的,有兩種方式能夠完成

一:使用上面說的CAST函數

update user set info=JSON_SET(info,'$.mail',CAST('{"a":"b"}' AS JSON )) where uid=5;

二:使用JSON_OBJECT 函數

update user set info=JSON_SET(info,'$.mail',JSON_OBJECT('a','b')) where uid=5;

能夠看到,顯然第一鍾比較爽,使用mybatis操做時,sql相似以下:

update user set info=json_set(info,'$.mail',CAST(#{a} AS JSON )) where id=1

參數便是pojo轉成的字符串

第二種參數是個變長數組,麻煩多了,見https://dev.mysql.com/doc/refman/5.7/en/json-creation-functions.html#function_json-object

修改數組

修改數組可使用JSON_ARRAY_APPEND()JSON_ARRAY_INSERT()

刪除

要刪除某個屬性,能夠經過JSON_REMOVE來操做便可

update user set info=JSON_REMOVE(info,'$.mailx') where uid=5;

其餘

  • 有了虛擬列,彷佛一張表只需包含一個int類型的id,一個json類型的content就好了,須要搜索和排序的經過創建虛擬列的方式,可是若是列比較多會增長mysql自己的維護成本

  • json的格式要固定,不能隨意更改,由於代碼是和格式強耦合的,若是變了那要大改,因此這就是mysql和mongodb這類nosql的一個區別,mysql並非無模式的,對於一張表的那個json字段,模式其實也是固化在json裏面而已

  • 對單體類修改,mybatis自動生成的類不能用,須要本身寫sql,屬性名稱千萬不能弄錯,沒有語法提示,錯了不容易發現

  • 開發成本(寫代碼)的成本增長

  • 維護的成本增長,若是後面須要對json裏面的另一個字段進行模糊查找和排序,那麼得增長虛擬列,重建索引,代碼也要改,而若是是結構化的數據,只要一個DDL便可

  • json看似更靈活,其實很是不靈活,遠不如結構的數據靈活

  • 經過update的語句,咱們其實能夠看出來部分修改的操做,好比JSON_SET,並非實際只修改部分的數據,而是把全量的數據加載到內存,而後修改部分數據,在把修改後的全量的數據設值到mysql中,只不過mysql提供了函數讓咱們方便的操做json而已

相關文章
相關標籤/搜索