本文分享自華為雲社群《GaussDB(DWS)——探究JSON,JSONB》,作者:yd_283975606。
1. 前言
- 適用版本:【8.1.1(及以上)】
JSON(JavaScript Object Notation)是一種輕量級的資料交換格式,常用於將資料從伺服器傳送到Web應用程式。它採用人類易讀和機器易解析的文字格式,基於鍵值對的集合,用於表示結構。
2. json/jsonb簡介
json演進歷程
版本 | |
---|---|
8.1.1 |
支援JSON資料型別 |
8.1.2 |
支援JSONB高階特性、索引 |
9.1.0 |
支援JSON列存、向量化,JSONB支援索引 |
2.1 json/jsonb簡介
參考DWS產品文件,JSON資料型別可以用來儲存JSON(JavaScript Object Notation)資料。
可以是單獨的一個標量,也可以是一個陣列,也可以是一個鍵值物件,其中陣列和物件可以統稱容器(container):
標量(scalar):單一的數字、bool、string、null都可以叫做標量。- 陣列(array):[]結構,裡面存放的元素可以是任意型別的JSON,並且不要求陣列內所有元素都是同一型別。
- 物件(object):{}結構,儲存key:value的鍵值對,其鍵只能是用“”包裹起來的字串,值可以是任意型別的JSON,對於重複的鍵,按最後一個鍵值對為準。
2.2 json與jsonb的區別
儲存方式json是輸入字串的完整複製,使用時再去解析,所以它會保留輸入的空格,重複鍵以及順序等;
jsonb解析後儲存,刪除語義無關的細節和重複的鍵,對鍵值也會進行排序,使用時不用再次解析。
效能差別json由於精確複製,因此插入時效能較好,但是其在處理函式時,必須在每個執行上重新解析,因此其查詢效能一般;
jsonb 資料以分解的二進位制格式儲存, 這使得它由於新增了轉換機制而在輸入上稍微慢些。但是其由於插入後即預設有序排列,因此可以更好地支援的額外操作(如bool關係的比較,頂層元素存在的判斷)。並且,其在處理函式時, 不需要重新解析,查詢效能較好。同時,jsonb支援建立btree、gist和gin索引。
3. json/jsonb輸入格式
1.標量(scalar):輸入為數字、布林型別時,使用單引號 ’ '宣告,輸入為字串時必須加 " "宣告
json_database=# SELECT '[1, 2, "foo", null, [[]], {}]'::jsonb; jsonb ------------------------------- [1, 2, "foo", null, [[]], {}] (1 row)
2.陣列(array):使用中括號[]包裹,滿足陣列書寫條件。陣列內元素型別可以是任意合法的JSON,且不要求型別一致。
json_database=# SELECT '[1, 2, "foo", null, [[]], {}]'::jsonb; jsonb ------------------------------- [1, 2, "foo", null, [[]], {}] (1 row)
3.物件(object):使用大括號{}包裹,鍵必須是滿足JSON字串規則的字串,值可以是任意合法的JSON。
json_database=# SELECT '{"a": 1, "b": {"a": 2, "b": null}}'::json; json ------------------------------------- {"a": 1, "b": {"a": 2, "b": null}} (1 row)
4.巢狀陣列和物件:陣列array中可以是任意合法的json元素,物件object則嚴格遵循了key:value的格式,兩者結合可以方便地有序查詢json值。
json_database=# SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::jsonb; jsonb ----------------------------------------------------- {"foo": [true, "bar"], "tags": {"a": 1, "b": null}} (1 row)
4. DWS的json與jsonb能力
當前DWS支援建立列存json、jsonb。
4.1 常用的json/jsonb函式及運算子(jsonb為例,json同理)
1.jsonb_object_field(jsonb, text)
描述:輸入的json型別為json-object,返回指定鍵對應的值(可能為json-object或json-array)
對應運算子:->
返回型別:jsonb
json_database=# SELECT jsonb_object_field('{"a": {"b":"foo"}}','a'); jsonb_object_field -------------------- {"b": "foo"} (1 row) json_database=# SELECT '{"a":{"b":"foo"}}'::jsonb->'a'; ?column? ------------- {"b":"foo"} (1 row)
2.jsonb_array_element(array-jsonb, integer)
描述:輸入的json型別為json-array,返回陣列中指定下標的元素(為任意合法的JSON)
對應運算子:->
返回型別:jsonb
json_database=# SELECT jsonb_array_element('[1,true,[1,[2,3]],null]',2); jsonb_array_element --------------------- [1, [2, 3]] (1 row) json_database=# SELECT '[1,true,[1,[2,3]],null]'::jsonb->2; ?column? ------------- [1, [2, 3]] (1 row)
3.jsonb_extract_path((jsonb, VARIADIC text[])
描述:輸入為json-object或json-array,返回$2所指路徑的值。$2中可以為json-object對應的鍵值(字串型別),也可以為json-array對應的下標(整數型別)
對應運算子:#>
注意:GaussDB(DWS)物件識別符號支援以符號"#“結尾,為避免a#>b解析過程出現歧義,因此運算子”#>"前後需要增加空格,否則解析報錯。
返回型別:jsonb
json_database=# SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":["stringy",1,true]}}', 'f4','f6',2); jsonb_extract_path -------------------- true (1 row) json_database=# SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":["stringy",1,true]}}'::jsonb #> '{f4,f6,2}'; ?column? ---------- true (1 row)
4.2 jsonb高階特性
1.jsonb會丟棄空格等語義無關的細節
json_database=# select ' [1, " a ", {"a" :1 }] '::jsonb; jsonb ---------------------- [1, " a ", {"a": 1}] (1 row)
2.jsonb會預設對輸入鍵值的重新排序
json_database=# insert into test_json values('{"C":1,"B":2,"A":false}','{"C":1,"B":2,"A":false}'); INSERT 0 1 json_database=# select *from test_json; jj | jb -------------------------+------------------------------ {"C":1,"B":2,"A":false} | {"A": false, "B": 2, "C": 1} (1 row)
比較規則如下:
首先比較型別:object-jsonb > array-jsonb > bool-jsonb > num-jsonb > str-jsonb > null-jsonb
同型別則比較內容:
- str-json型別:依據text比較的方法,使用資料庫預設排序規則進行比較,返回值正數代表大於,負數代表小於,0表示相等。
- num-json型別:數值比較
- bool-json型別:true > false
- array-jsonb型別:長度長的 > 長度短的,長度相等則依次比較每個元素。
- object-jsonb型別:長度長的 > 長度短的,長度相等則依次比較每個鍵值對,先比較鍵,再比較值。
5.總結
DWS的JSON能力總結
目前,DWS的JSON/JSONB的功能基本完善。主要體現在函式、運算子、索引功能的支援。但目前來說,JSON列存仍然採用的是直接儲存JSON資料,即將原始的JSON資料存成單獨的一列,以完整的JSON值作為最小的粒度在磁碟上,具體如下:
json_data |
---|
{“user_id”:1001, “user_name”: “Adam”, “gender”: “Male”, “age”: 16} |
{“user_id”:1002, “user_name”: “Bob”, “gender”: “Male”, “age”: 41} |
{“user_id”:1003, “user_name”: “Clair”, “gender”: “Female”, “age”: 21} |
優點是:JSON則天然支援Schema Evoluation,上游業務的變更,只需要在JSON列資料中進行增刪相應的欄位,無需對數倉中的表做任何DDL就能完成,也能對中間的ETL作業做到透明,最大程度地保留了半結構化資料的易用性和靈活性,能大大降低維護和管理表結構的成本。
缺點是:應用端查詢時需要選擇合適的處理函式和方法,才能解析到需要的資料,開發較為複雜,如果JSON較複雜,同時查詢效能會有退化,因為每次JSON列的資料參與計算的時候,都需要對JSON資料完整的解析一遍,比如需要抽取出整個JSON中某個欄位,那麼查詢引擎執行的時候就要讀出每一行JSON,解析一遍,取出需要的欄位再返回。這中間會涉及大量的IO和計算,而需要的可能只是JSON資料成百上千欄位當中的一個欄位,這中間的大量IO和計算都是浪費的。
另外,當前雲原生分支上JSON的向量化支援仍然是沿用的通用的向量化框架,沒有定製化的向量化函式。通用的向量化函式框架本質上來說仍然為行存的呼叫,並不是完全意義上的向量化。
後續演進路線
如上所述,後續想要提升JSON/JSONB的查詢效能,首先必須提升JSON的儲存方式,即在解析前端將JSON拍平成寬表,真正意義上發揮JSON半結構化資料的優勢。
user_id |
user_name |
gender |
age |
---|---|---|---|
1001 |
Adam |
Male |
16 |
1002 |
Bob |
Male |
41 |
1003 |
Clair |
Female |
21 |
這種做法的優點是:寫入DWS時,因為是普通列寫入,所以寫入效能會更好,同時在查詢側,不需要對JSON資料進行解析,查詢效能也會更好。
缺點是:每當上游的資料格式有變更時,比如變更資料型別、增刪欄位、執行DDL進行加列或者刪列,中間的實時資料ETL作業也需要進行適配改動並重新上線,使用非常不靈活,也會額外增加運維和開發負擔。並且當JSON的每一個鍵值都為一列,若出現異常資料,可能導致列數的急速膨脹,進而影響效能。
當前Hologres的方案類似,但其對特殊的列採用單獨列(屬性為JSON)儲存那些同質化不強的json鍵值(極少數出現的json鍵值)
另外,當前列存JSON的效能當前瓶頸點在於向量化的效能,一方面需要提升通用當前DWS的向量化能力,另一方面也可以考慮對json函式做出最佳化。
點選關注,第一時間瞭解華為雲新鮮技術~