說實話,我的不是很喜歡這種特性,關係型數據庫就應該有關係型數據庫的樣子,而不是爲了留住用戶而強加給他一些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 |
Append data to JSON document |
|
Insert into JSON array |
|
Return value from JSON column after evaluating path; equivalent to JSON_EXTRACT(). |
|
Whether JSON document contains specific object at path |
|
Whether JSON document contains any data at path |
|
Maximum depth of JSON document |
|
Return data from JSON document |
|
Return value from JSON column after evaluating path and unquoting the result; equivalent to JSON_UNQUOTE(JSON_EXTRACT()). |
|
Insert data into JSON document |
|
Array of keys from JSON document |
|
Number of elements in JSON document |
|
JSON_MERGE() (deprecated 5.7.22) |
Merge JSON documents, preserving duplicate keys. Deprecated synonym for JSON_MERGE_PRESERVE() |
Merge JSON documents, replacing values of duplicate keys |
|
Merge JSON documents, preserving duplicate keys |
|
Create JSON object |
|
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. |
|
Quote JSON document |
|
Remove data from JSON document |
|
Replace values in JSON document |
|
Path to value within JSON document |
|
Insert data into JSON document |
|
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 |
|
Type of JSON value |
|
Unquote JSON value |
|
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而已