拋磚系列之-MySQL中的數據類型JSON


 

 

 今天介紹一個MySQL中的數據類型-JSON,相信大家對JSON都不陌生,在日常工作中使用到的頻率也很高,話不多說,直接開始。

 


何謂JSON

看下RFC文檔對於JSON的描述

1.基於 JavaScript 語言的輕量級的數據交換格式

2.基於文本

3.語言無關

 


JSON應用場景

我大概使用過以下兩類:

1.接口的數據交換,比如ajax請求時的application/json、rpc調用時的JSON序列化\反序列化;

2.以JSON格式存儲數據,我接觸過以下兩種:

    2.1 以Mongodb為代表的文檔型數據庫,很好的支持JSON格式的數據存儲;

    2.2 以MySQL為代表的關系型數據庫,5.7.8之前沒有JSON這種數據類型,只能以varchar或者text形式變相的支持JSON,存取鍵值極不方便;5.7.8開始有JSON數據類型,有專門語法支持鍵值的存取,易用性得到很大提升。

 

接下來重點聊聊MySQL中如何存取JSON以及存在的一些問題。

                                        


MySQL 存儲JSON

熟悉關系型數據庫的同學都知道,數據存儲在表中,得先有表才能插數據,看一條普通的SQL insert語句

insert into user(id,name,age) values(1,'jack',10);

代表的語義是往user表中插入一條數據,這條數據有三個屬性,分別是id、name、age,各自對應MySQL user表中的三個列,如果我們向user表中插入一個不存在的列salary,MySQL會報錯

Error Code: 1054. Unknown column 'salary' in 'field list'

結論是要往MySQL表中插入數據,必須提前定義好表結構,表結構包括表名、表字符集、表包含的字段、字段名、字段類型等等。

 

有什么辦法能不給表加物理字段就可以為數據增加屬性呢?

給表預置一個擴展字段是一種解決思路,比如extdata,里面存儲JSON形式的鍵值對,形如:

 

extdata

{"salary":1000,
"sex":'女',
"其他key":'其他值'
}

至於存哪些key完全由使用方決定,key的數量不限,value的類型也不限,是不是有很好的擴展性,不管業務怎么變,底層存儲都是支持的。

 

這也就是為什么要在MySQL中存取JSON的目的,主要是為了追求擴展性。

 

具體到MySQL中怎么實現,前面提到MySQL 5.7.8之前是不支持JSON的,要支持JSON語義,只能以字符串形式來變相實現,比如要修改extdata中的salary為2000,是沒有辦法直接修改的,需要先在應用層將extdata讀出然后反序列化為JSON對象,通過JSON對象的Api來修改salary的值,修改完以后將新的JSON對象序列化為新JSON串,最后整體修改user表中的extdata字段為新JSON串,用代碼實現大體如下:

1.result = db.execute("select extdata from user where id = xxx");
2.JSONObj = JSONUtil.parse(result.get("extdata"));
3.JSONObj.put("salary",2000);
4.extdata_str = JSONObj.toJSONString();
5.db.execute("update user set extdata=extdata_str where id=xxx");

  

這一套更新操作繁瑣且性能低,讀取操作也存在類似問題,由於沒有原生Api的支持,這一切感覺有點糟糕。

到了MySQL 5.7.8開始,MySQL開始支持JSON這種數據類型,看下官方文檔的介紹:

MySQL新增加的原生JSON類型比在字符串列中存儲 JSON 格式的字符串相比有兩個優點:

    1.自動的數據校驗,對於JSON類型的列MySQL會校驗其合法性;

    2.提供了更方便的Api用於存取,避免了繁瑣的應用層操作。

 

看下基於MySQL 5.7.8,如何優雅的存取JSON類型中的鍵值,依然以修改extdata中的salary為例:

update user set extdata = JSON_SET(user.extdata, '$.salary',2000) where id =1;

 讀取salary的值:

select JSON_EXTRACT(user.extdata, '$.salary') from user where id =1;

 借助JSON_SET和JSON_EXTRACT這兩個Api,極大的降低了存取的復雜度,想深入了解MySQL JSON用法的請參考文章最后的推薦閱讀內容。

 

說到這兒,借助MySQL的原生JSON類型以及相關的Api存取擴展數據在易用性方面已經沒什么問題了,接下來從性能角度思考下是否有待提升。 

/*找出salary等於2000的user*/
select * from user where JSON_EXTRACT(user.extdata, '$.salary') =2000;

 在我自己的pc機上,user表中共300萬條數據,執行這條SQL花費接近3秒,不談快慢,就論是否有優化空間,貼個執行計划出來

 

面對大名鼎鼎的全表掃描如何優化呢?


 

 優化JSON查詢

按照過往的思路,我們只要設計合理的索引就能避免全表掃描,但這次面對JSON似乎有點黔驢技窮了,別擔心,大名鼎鼎的MySQL早已幫你做了既生瑜又生亮的美事,看看官方怎么說。

  1. JSON類型列無法直接索引;

  2. 可以基於JSON創建一個生成列,然后基於生成列創建索引,從而達到對JSON類型列加索引的效果。

接着看下何謂生成列

 

生成列的值在插入數據時不需要設置,MySQL會根據生成列關聯的表達式自動計算填充,生成列的定義方式如下:

col_name data_type [GENERATED ALWAYS] AS (expr)
  [VIRTUAL | STORED] [NOT NULL | NULL]
  [UNIQUE [KEY]] [[PRIMARY] KEY]
  [COMMENT 'string']

AS (expr)指示生成列並定義用於計算列值的表達式,可以在前面加上GENERATED ALWAYS明確的表示這是一個生成列。  

 

回歸到我們的場景中,分三步進行優化:

1.創建一個生成列v_salary,計算列值表達式為extdata->"$.salary",代表提取extdata中的salary值
ALTER TABLE `user` ADD COLUMN `v_salary` DECIMAL(10,2) as (extdata->"$.salary") AFTER `extdata`;
2.針對v_salary創建索引
ALTER TABLE `user` ADD INDEX `idx_salary` (`v_salary`) ;
3.替換查詢語句中JSON_EXTRACT(user.extdata, '$.salary')為v_salary;
select * from user where v_salary =2000

select * from user where v_salary =2000,執行耗時為0.047s,這個優化效果非常顯著。

看下現在的執行計划已經使用了索引

 

 

 


總結

任何新技術的引入一定要有一個比較全面的認識,充分理解其利弊,不能只看到其光鮮的一面,而忽略其帶來的弊端,對於弊端要有應對措施,知己知彼。

 


推薦閱讀

 https://dev.mysql.com/doc/refman/5.7/en/json.html

 https://dev.mysql.com/doc/refman/5.7/en/json-modification-functions.html#function_json-set

 https://dev.mysql.com/doc/refman/5.7/en/create-table-secondary-indexes.html

 https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html

 rfc7159 (ietf.org)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM