PostgreSQL 9.4 正在載入一項新功能叫jsonb,是一種新型資料,可以儲存支援GIN索引的JSON 資料。換言之,此功能,在即將來臨的更新中最重要的是,如果連這都不重要的話,那就把Postgres 置於檔案為本資料庫系統的推薦位置吧。
自從9.2開始,一個整合JSON 資料類型已經存在,帶有一整套功能(例如資料產生和資料解構功能),還有9.3新增的操作者。當使用JSON 資料類型,資料的被儲存成一完全一樣的副本,功能還在此之上運作,還另外需要後台運作的重新分析。
這心得JSONB 資料類型以已降解的2元格式儲存,所以,插入此資料會比JSON高效,因為後台不再需要重新分析,因此讓它更快速運行,而且還兼顧GIN 索引。就是因為最後這個原因,我們實際上建議讀者使用jsonb來代替json製作程式(當然你還可以因應需要而使用json)。請記住jsonb使用相同的操作者和功能,讀者們可以看我之前的文章去令你得到些什麼啟發(或者乾脆看Postgres的檔案)。
現在讓我們看一下JSONB是如何工作的,同時和JSON比較一下。採用的測試資料是860萬的geobase類型資料,大概1.1G大小,包括了城市名,國家代碼(可以在這參見完整列表)等很多欄位。首先通過底層複製(raw copy)來把這些資料存放區到資料庫的一個新表裡面,之後把這張表通過一組填滿因數是100的錶轉換成JSON/JSONB,之後來看它們各佔多少空間。
=# COPY geodata FROM '$HOME/Downloads/allCountries.txt';COPY 8647839=# CREATE TABLE geodata_jsonb (data jsonb) with (fillfactor=100);CREATE TABLE=# CREATE TABLE geodata_json (data json) with (fillfactor=100);CREATE TABLE=# \timingTiming is on.=# INSERT INTO geodata_json SELECT row_to_json(geodata) FROM geodata;INSERT 0 8647839Time: 287158.457 ms=# INSERT INTO geodata_jsonb SELECT row_to_json(geodata)::jsonb FROM geodata;INSERT 0 8647839Time: 425825.967 ms
產生JSONB資料花費稍微長一點時間,大小有沒有區別呢?
=# SELECT pg_size_pretty(pg_relation_size('geodata_json'::regclass)) AS json, pg_size_pretty(pg_relation_size('geodata_jsonb'::regclass)) AS jsonb; json | jsonb ---------+--------- 3274 MB | 3816 MB(1 row)
在JSON資料上面做索引從9.3版本開始,比如用操作符(注意 因為它返迴文本,所以'->>'被採用;並且根據查詢不同,索引採用不同的關鍵字)
=# CREATE INDEX geodata_index ON geodata_json ((data->>'country_code'), (data->>'asciiname'));CREATE INDEX=# SELECT pg_size_pretty(pg_relation_size('geodata_index'::regclass)) AS json_index; json_index ------------ 310 MB(1 row)=# SELECT (data->>'population')::int as population, data->'latitude' as latitude, data->'longitude' as longitude FROM geodata_json WHERE data->>'country_code' = 'JP' AND data->>'asciiname' = 'Tokyo' AND (data->>'population')::int != 0; population | latitude | longitude ------------+----------+----------- 8336599 | 35.6895 | 139.69171(1 row)=# -- Explain of previous query QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on geodata_json (cost=6.78..865.24 rows=215 width=32) Recheck Cond: (((data ->> 'country_code'::text) = 'JP'::text) AND ((data ->> 'asciiname'::text) = 'Tokyo'::text)) Filter: (((data ->> 'population'::text))::integer <> 0) -> Bitmap Index Scan on geodata_index (cost=0.00..6.72 rows=216 width=0) Index Cond: (((data ->> 'country_code'::text) = 'JP'::text) AND ((data ->> 'asciiname'::text) = 'Tokyo'::text)) Planning time: 0.172 ms(6 rows)
在這個例子裡,計劃(planner)可以使用bitmap索引掃描,同時使用了之前產生的索引。
現在,JSONB的一個新特點就是檢查包含帶有操作符@>的資料容量,這種資料是可以用GIN來索引的,這種操作符資料也包括了?,?|和?&(為了檢查給定的關鍵字是否存在)。 GIN索引對兩類操作符起作用:
預設操作符類,之前列出的四個;
jsonb_hash_ops,僅支援@>,但是當搜尋資料時效能表現不錯,而且所佔磁碟空間較小;
下面是它如何工作:
=# CREATE INDEX geodata_gin ON geodata_jsonb USING GIN (data jsonb_hash_ops);CREATE INDEX=# SELECT (data->>'population')::int as population, data->'latitude' as latitude, data->'longitude' as longitude FROM geodata_jsonb WHERE data @> '{"country_code": "JP", "asciiname": "Tokyo"}' AND (data->>'population')::int != 0; population | latitude | longitude ------------+----------+----------- 8336599 | 35.6895 | 139.69171(1 row) =# SELECT pg_size_pretty(pg_relation_size('geodata_gin'::regclass)) AS jsonb_gin; jsonb_gin----------- 1519 MB(1 row)=# -- EXPLAIN of previous query QUERY PLAN ------------------------------------------------------------------------------------- Bitmap Heap Scan on geodata_jsonb (cost=131.01..31317.76 rows=8605 width=418) Recheck Cond: (data @> '{"asciiname": "Tokyo", "country_code": "JP"}'::jsonb) Filter: (((data ->> 'population'::text))::integer <> 0) -> Bitmap Index Scan on geodata_gin (cost=0.00..128.86 rows=8648 width=0) Index Cond: (data @> '{"asciiname": "Tokyo", "country_code": "JP"}'::jsonb) Planning time: 0.134 ms
根據應用的需求,你或許想採用空間消耗低的索引,比如BTree建立在JSON資料上的索引類型;GIN索引有著更多的優點,因為它覆蓋了所有的JSON欄位,並且檢查容量;