Какво е JSON?
JSON означава „JavaScript Object Notation“, който е вид формат на данни, популярно използван от уеб приложенията. Това означава, че данните ще се предават между уеб приложения и сървъри в такъв формат. JSON беше въведен като алтернатива на XML формата. В „добрите стари дни“ данните, използвани за предаване в XML формат, който е тежък тип данни в сравнение с JSON. По-долу е даден пример за JSON форматиран низ:
{ "ID":"001","name": "Ven", "Country": "Australia", "city": "Sydney", "Job Title":"Database Consultant"}
JSON низ може да съдържа друг JSON обект в себе си, както е показано по-долу:
{ "ID":"001", "name": "Ven", "Job Title":"Database Consultant", "Location":{"Suburb":"Dee Why","city": "Sydney","State":"NSW","Country": "Australia"}}
Съвременните уеб и мобилни приложения генерират предимно данните във формат JSON, наричани още „JSON байтове“, които се улавят от сървърите на приложения и се изпращат към базата данни. JSON байтовете се обработват на свой ред, разбиват се на отделни стойности на колони и се вмъкват в RDBMS таблица.
Пример:
{ "ID":"001","name": "Ven", "Country": "Australia", "city": "Sydney", "Job Title":"Database Consultant"}
Горните JSON данни се преобразуват в SQL, както е по-долу...
Insert into test (id, name, country,city,job_title) values (001,'Ven','Australia','Sydney','Database Consultant');
Когато става въпрос за съхранение и обработка на JSON данните, има различни NoSQL бази данни, които го поддържат и най-популярната е MongoDB. Що се отнася до базите данни на RDBMS, до скоро JSON низовете се третираха като нормален текст и нямаше типове данни, които конкретно разпознават, съхраняват или обработват низове във формат JSON. PostgreSQL, най-популярната база данни за RDBMS с отворен код, излезе с тип данни JSON, който се оказа изключително полезен за производителността, функционалността и мащабируемостта, когато става въпрос за работа с JSON данни.
PostgreSQL + JSON
Базата данни PostgreSQL става все по-популярна откакто беше въведен типът данни JSON. Всъщност PostgreSQL превъзхожда MongoDB, когато става въпрос за обработка на голямо количество JSON данни. Приложенията могат да съхраняват JSON низове в базата данни PostgreSQL в стандартния JSON формат. Разработчиците просто трябва да кажат на приложението да изпрати през JSON низовете към базата данни като json тип данни и да извлече обратно във формат JSON. Съхраняването на JSON низ в тип данни JSON има няколко предимства в сравнение със съхраняването на същия в тип данни TEXT. Типът данни JSON може да приема само валидни JSON форматирани низове, ако низът не е в правилен JSON формат, се генерира грешка. Типът данни JSON помага на приложението да извършва ефикасни търсения, базирани на индекси, които скоро ще видим подробно.
Типът данни JSON беше въведен в PostgreSQL-9.2 публикацията, която бяха направени значителни подобрения. Основното допълнение се появи в PostgreSQL-9.4 с добавянето на тип данни JSONB. JSONB е усъвършенствана версия на JSON тип данни, която съхранява JSON данните в двоичен формат. Това е основното подобрение, което направи голяма разлика в начина, по който JSON данните се търсят и обработват в PostgreSQL. Нека разгледаме подробно предимствата на JSON типовете данни.
Типове данни JSON и JSONB
Типът данни JSON съхранява json форматирани низове като текст, който не е много мощен и не поддържа много функции, свързани с JSON, използвани за търсения. Той поддържа само традиционното B-TREE индексиране и не поддържа други типове индекси, които са наложителни за по-бързи и ефективни операции за търсене в JSON данни.
JSONB, разширената версия на типа данни JSON, силно се препоръчва за съхранение и обработка на JSON документи. Той поддържа широк спектър от json оператори и има многобройни предимства пред JSON, като съхраняване на JSON форматирани низове в двоичен формат и поддръжка на JSON функции и индексиране за извършване на ефективни търсения.
Нека разгледаме разликите.
JSON | JSONB | |
---|---|---|
1 | Доста като тип данни TEXT, който съхранява само валиден JSON документ. | Съхранява JSON документите в двоичен формат. |
2 | Съхранява JSON документите такива, каквито са, включително бели интервали. | Отрязва белите пространства и съхранява във формат, удобен за по-бързо и ефективно търсене |
3 | Не поддържа индексиране с ПЪЛНО ТЪКСВО ТЪРСЕНЕ | Поддържа индексиране с ПЪЛНО ТЪКСВО ТЪРСЕНЕ |
4 | Не поддържа широк спектър от JSON функции и оператори | Поддържа всички JSON функции и оператори |
Пример за №4, посочен по-горе
JSON
По-долу е дадена таблица с тип данни JSON
dbt3=# \d product
Table "dbt3.product"
Column | Type | Collation | Nullable | Default
----------------+--------+-----------+----------+---------
item_code | bigint | | not null |
productdetails | json | | |
Indexes:
"product_pkey" PRIMARY KEY, btree (item_code)
Не поддържа традиционни JSON оператори (като “@>” или “#>”). Търсенето в пълен текст чрез JSON данни се извършва с помощта на “@>” или “#>” в SQL, който не се поддържа от JSON тип данни
dbt3=# select * from product where productdetails @> '{"l_shipmode":"AIR"}' and productdetails @> '{"l_quantity":"27"}';
ERROR: operator does not exist: json @> unknown
LINE 1: select * from product where productdetails @> '{"l_shipmode"...
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
dbt3=#
JSONB
По-долу е дадена таблица с тип данни JSONB
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | jsonb | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
Поддържа ПЪЛНО ТЪРСЕНЕ НА ТЕКСТ чрез JSON данни с помощта на оператори (като „@>“)
dbt3=# select * from products where order_details @> '{"l_shipmode" : "AIR"}' limit 2;
item_code | order_details
-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4 | {"l_partkey": 21315, "l_orderkey": 1, "l_quantity": 28, "l_shipdate": "1996-04-21", "l_shipmode": "AIR", "l_commitdate": "1996-03-30", "l_shipinstruct": "NONE", "l_extendedprice": 34616.7}
8 | {"l_partkey": 42970, "l_orderkey": 3, "l_quantity": 45, "l_shipdate": "1994-02-02", "l_shipmode": "AIR", "l_commitdate": "1994-01-04", "l_shipinstruct": "NONE", "l_extendedprice": 86083.6}
(2 rows)
Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книга Как да заявя JSON данни
Нека да разгледаме някои възможности на PostgreSQL JSON, свързани с операциите с данни По-долу е как изглеждат JSON данните в таблица. Колона „order_details“ е от тип JSONB
dbt3=# select * from product_details ;
item_code | order_details
-----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | {"l_partkey": 1551894, "l_orderkey": 1, "l_quantity": 17, "l_shipdate": "1996-03-13", "l_shipmode": "TRUCK", "l_commitdate": "1996-02-12", "l_shipinstruct": "DELIVER IN PERSON", "l_extendedprice": 33078.9}
2 | {"l_partkey": 673091, "l_orderkey": 1, "l_quantity": 36, "l_shipdate": "1996-04-12", "l_shipmode": "MAIL", "l_commitdate": "1996-02-28", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 38306.2}
3 | {"l_partkey": 636998, "l_orderkey": 1, "l_quantity": 8, "l_shipdate": "1996-01-29", "l_shipmode": "REG AIR", "l_commitdate": "1996-03-05", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 15479.7}
4 | {"l_partkey": 21315, "l_orderkey": 1, "l_quantity": 28, "l_shipdate": "1996-04-21", "l_shipmode": "AIR", "l_commitdate": "1996-03-30", "l_shipinstruct": "NONE", "l_extendedprice": 34616.7}
5 | {"l_partkey": 240267, "l_orderkey": 1, "l_quantity": 24, "l_shipdate": "1996-03-30", "l_shipmode": "FOB", "l_commitdate": "1996-03-14", "l_shipinstruct": "NONE", "l_extendedprice": 28974}
6 | {"l_partkey": 156345, "l_orderkey": 1, "l_quantity": 32, "l_shipdate": "1996-01-30", "l_shipmode": "MAIL", "l_commitdate": "1996-02-07", "l_shipinstruct": "DELIVER IN PERSON", "l_extendedprice": 44842.9}
7 | {"l_partkey": 1061698, "l_orderkey": 2, "l_quantity": 38, "l_shipdate": "1997-01-28", "l_shipmode": "RAIL", "l_commitdate": "1997-01-14", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 63066.3}
8 | {"l_partkey": 42970, "l_orderkey": 3, "l_quantity": 45, "l_shipdate": "1994-02-02", "l_shipmode": "AIR", "l_commitdate": "1994-01-04", "l_shipinstruct": "NONE", "l_extendedprice": 86083.6}
9 | {"l_partkey": 190355, "l_orderkey": 3, "l_quantity": 49, "l_shipdate": "1993-11-09", "l_shipmode": "RAIL", "l_commitdate": "1993-12-20", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 70822.1}
10 | {"l_partkey": 1284483, "l_orderkey": 3, "l_quantity": 27, "l_shipdate": "1994-01-16", "l_shipmode": "SHIP", "l_commitdate": "1993-11-22", "l_shipinstruct": "DELIVER IN PERSON", "l_extendedprice": 39620.3}
(10 rows)
Изберете всички кодове на артикули, включително датите на доставка
dbt3=# select item_code, order_details->'l_shipdate' as shipment_date from product_details ;
item_code | shipment_date
-----------+---------------
1 | "1996-03-13"
2 | "1996-04-12"
3 | "1996-01-29"
4 | "1996-04-21"
5 | "1996-03-30"
6 | "1996-01-30"
7 | "1997-01-28"
8 | "1994-02-02"
9 | "1993-11-09"
10 | "1994-01-16"
(10 rows)
Вземете кода на артикул, количеството и цената на всички поръчки, пристигнали по въздух
dbt3=# select item_code, order_details->'l_quantity' as quantity, order_details->'l_extendedprice' as price, order_details->'l_shipmode' as price from product_details where order_details->>'l_shipmode'='AIR';
item_code | quantity | price | price
-----------+----------+---------+-------
4 | 28 | 34616.7 | "AIR"
8 | 45 | 86083.6 | "AIR"
(2 rows)
JSON операторите “->” и “->>” се използват за избор и сравнения в SQL заявката. Операторът “->” връща полето за JSON обект като поле в кавички, а операторът “->>” връща полето за JSON обект като ТЕКСТ. Горните два SQL-а са примери за показване на стойности на JSON полета такива, каквито са. По-долу е даден пример за извличане на полето JSON във формата TEXT.
По-долу е даден пример за извличане на полето JSON под формата на TEXT
dbt3=# select item_code, order_details->>'l_shipdate' as shipment_date from product_details ;
item_code | shipment_date
-----------+---------------
1 | 1996-03-13
2 | 1996-04-12
3 | 1996-01-29
4 | 1996-04-21
5 | 1996-03-30
6 | 1996-01-30
7 | 1997-01-28
8 | 1994-02-02
9 | 1993-11-09
10 | 1994-01-16
(10 rows)
Има друг оператор, наречен „#>“, който се използва за запитване на частта от данни на JSON елемент, който на свой ред е част от JSON низ. Нека разгледаме един пример.
По-долу са данните в таблицата.
dbt3=# select * from test_json ;
id | details
-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
10000 | {"Job": "Database Consultant", "name": "Venkata", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Dee Why", "Country": "Australia"}}
20000 | {"Job": "Database Consultant", "name": "Smith", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Manly", "Country": "Australia"}}
30000 | {"Job": "Developer", "name": "John", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Brookvale", "Country": "Australia"}}
50000 | {"cars": {"Ford": [{"doors": 4, "model": "Taurus"}, {"doors": 4, "model": "Escort"}], "Nissan": [{"doors": 4, "model": "Sentra"}, {"doors": 4, "model": "Maxima"}, {"doors": 2, "model": "Skyline"}]}}
40000 | {"Job": "Architect", "name": "James", "Location": {"city": "Melbourne", "State": "NSW", "Suburb": "Trugnania", "Country": "Australia"}}
Искам да видя всички подробности с „State“ „NSW“ и „State“ е JSON обектният ключ, който е част от ключа „Location“. По-долу е как да направите заявка за същото.
dbt3=# select * from test_json where details #> '{Location,State}'='"NSW"';
id | details
-------+------------------------------------------------------------------------------------------------------------------------------------------------
10000 | {"Job": "Database Consultant", "name": "Venkata", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Dee Why", "Country": "Australia"}}
20000 | {"Job": "Database Consultant", "name": "Smith", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Manly", "Country": "Australia"}}
30000 | {"Job": "Developer", "name": "John", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Brookvale", "Country": "Australia"}}
30000 | {"Job": "Architect", "name": "James", "Location": {"city": "Melbourne", "State": "NSW", "Suburb": "Trugnania", "Country": "Australia"}}
(4 rows)
Аритметичните операции могат да се извършват върху JSON данни. Необходимо е прехвърляне на типа, тъй като частта с данни в колоната JSON е TEXT.
dbt3=# select item_code, order_details->'l_quantity' as quantity, order_details->'l_extendedprice' as price, order_details->'l_shipmode' as price from product_details where (order_details->'l_quantity')::int > 10;
item_code | quantity | price | price
-----------+----------+---------+---------
1 | 17 | 33078.9 | "TRUCK"
2 | 36 | 38306.2 | "MAIL"
4 | 28 | 34616.7 | "AIR"
5 | 24 | 28974 | "FOB"
6 | 32 | 44842.9 | "MAIL"
7 | 38 | 63066.3 | "RAIL"
8 | 45 | 86083.6 | "AIR"
9 | 49 | 70822.1 | "RAIL"
10 | 27 | 39620.3 | "SHIP"
(9 rows)
Освен всичко по-горе, следните операции могат да се извършват и върху JSON с помощта на SQL, включително JOIN
- Сортиране на данните с помощта на клауза ORDER BY
- Агрегиране с помощта на агрегатни функции като SUM, AVG, MIN, MAX и т.н.
- Групирайте данните с помощта на клауза GROUP BY
Какво ще кажете за производителността?
Данните в колоните на JSON ще бъдат от текстов характер и въз основа на размера на данните могат да се очакват проблеми с производителността. Търсенето в JSON данни може да отнеме време и изчислителна мощност, което води до бавни отговори на приложението(ата). За DBA е наложително да гарантират, че SQLs, удрящи колоните на JSON, реагират достатъчно бързо и осигуряват добра производителност. Тъй като извличането на данни се извършва чрез SQL, опцията, която DBA ще търсят, е възможността за индексиране и да, типовете данни JSON поддържат опции за индексиране.
Нека да разгледаме опциите за индексиране, които ни предлага JSON.
Индексиране на JSONB
Типът данни JSONB поддържа индексиране с ПЪЛНО ТЕКСТ. Това е най-важната възможност на JSONB, която DBA ще очакват с нетърпение, когато използват JSONB типове данни. Нормален индекс на JSON обектен ключ може да не помогне, когато използвате специфични за JSON оператори в заявките за търсене. По-долу е заявка TEXT SEARCH, която се отнася за FULL-TABLE-SCAN
dbt3=# explain select * from products where order_details @> '{"l_shipmode" : "AIR"}';
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on products (cost=0.00..4205822.65 rows=59986 width=252)
Filter: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
(2 rows)
JSONB поддържа тип индекс FULL-TEXT-SEARCH, наречен GIN, който помага на заявки като по-горе.
Сега нека създам GIN индекс и да видя дали това помага
dbt3=# create index od_gin_idx on products using gin(order_details jsonb_path_ops);
CREATE INDEX
Ако можете да наблюдавате по-долу, заявката взема GIN индекс
dbt3=# explain select * from products where order_details @> '{"l_shipmode" : "AIR"}';
QUERY PLAN
-------------------------------------------------------------------------------
Bitmap Heap Scan on products (cost=576.89..215803.18 rows=59986 width=252)
Recheck Cond: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
-> Bitmap Index Scan on od_gin_idx (cost=0.00..561.90 rows=59986 width=0)
Index Cond: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
И индекс B-TREE вместо GIN НЯМА да помогне
dbt3=# create index idx on products((order_details->>'l_shipmode'));
CREATE INDEX
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | jsonb | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
"idx" btree ((order_details ->> 'l_shipmode'::text))
Можете да видите по-долу, заявката предпочита FULL-TABLE-SCAN
dbt3=# explain select * from products where order_details @> '{"l_shipmode" : "AIR"}';
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on products (cost=0.00..4205822.65 rows=59986 width=252)
Filter: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
Какво е GIN индекс?
GIN означава обобщен обърнат индекс. Основната способност на GIN Index е да ускори пълнотекстово търсене. Когато извършвате търсене въз основа на конкретни ключове или елементи в ТЕКСТ или документ, GIN Index е правилният начин. GIN Index съхранява двойки „Ключ“ (или елемент или стойност) и „списък с позиции“. Списъкът с позиции е идентификаторът на реда на ключа. Това означава, че ако „Ключът“ се среща на множество места в документа, GIN Index съхранява ключа само веднъж заедно с неговата позиция на събития, което не само поддържа GIN Index компактен по размер, но също така помага за ускоряване на търсенията в страхотно начин. Това е подобрението в Postgres-9.4.
Предизвикателства с GIN индекс
В зависимост от сложността на данните, поддържането на GIN индекси може да бъде скъпо. Създаването на GIN индекси отнема време и ресурси, тъй като индексът трябва да търси в целия документ, за да намери ключовете и техните идентификатори на редове. Може да бъде още по-трудно, ако индексът на GIN е подут. Освен това размерът на GIN индекса може да бъде много голям въз основа на размера и сложността на данните.
Индексиране на JSON
JSON не поддържа текстово търсене и индекси като GIN
dbt3=# create index pd_gin_idx on product using gin(productdetails jsonb_path_ops);
ERROR: operator class "jsonb_path_ops" does not accept data type json
Нормалното индексиране като B-TREE се поддържа както от JSON, така и от JSONB
Да, нормалните индекси като B-TREE Index се поддържат от JSON и JSONB типове данни и не са подходящи за операции за текстово търсене. Всеки JSON обектен ключ може да бъде индексиран поотделно, което наистина би помогнало САМО когато същият обектен ключ се използва в клаузата WHERE.
Нека създам B-TREE Index на JSONB и да видя как работи
dbt3=# create index idx on products((order_details->>'l_shipmode'));
CREATE INDEX
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | jsonb | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
"idx" btree ((order_details ->> 'l_shipmode'::text))
Вече научихме по-горе, че индексът B-TREE НЕ е полезен за ускоряване на SQLs, извършващи ПЪЛНО ТЪРСЕНЕ НА JSON данни с помощта на оператори (като „@>”) и такива индекси САМО биха помогнали за ускоряване на заявките като този по-долу, които са типични SQLs от тип RDBMS (които не са заявки за търсене). Всеки от JSON обектния ключ може да бъде индексиран поотделно, което би помогнало на заявките да се ускорят, когато тези индексирани JSON обектни ключове се използват клаузата WHERE.
Примерът по-долу за заявка използва обектен ключ „l_shipmode“ в клаузата WHERE и тъй като то е индексирано, заявката отива за индексно сканиране. Ако искате да търсите, използвайки различен ключ на обекта, тогава заявката ще избере да извърши ПЪЛНО-ТАБЛИЧНО СКАНИРАНЕ.
dbt3=# explain select * from products where order_details->>'l_shipmode'='AIR';
QUERY PLAN
---------------------------------------------------------------------------------
Index Scan using idx on products (cost=0.56..1158369.34 rows=299930 width=252)
Index Cond: ((order_details ->> 'l_shipmode'::text) = 'AIR'::text)
Същото работи и с типа данни JSON
dbt3=# create index idx on products((order_details->>'l_shipmode'));
CREATE INDEX
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | json | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
"idx" btree ((order_details ->> 'l_shipmode'::text))
Ако можете да наблюдавате, заявката използва индекса
dbt3=# explain select * from products where order_details->>'l_shipmode'='AIR';
QUERY PLAN
---------------------------------------------------------------------------------
Index Scan using idx on products (cost=0.56..1158369.34 rows=299930 width=252)
Index Cond: ((order_details ->> 'l_shipmode'::text) = 'AIR'::text)
Заключение
Ето някои неща, които трябва да запомните, когато използвате PostgreSQL JSON данни...
- PostgreSQL е една от най-добрите опции за съхранение и обработка на JSON данни
- С всички мощни функции PostgreSQL може да бъде вашата база данни за документи
- Виждал съм архитектури, при които са избрани две или повече хранилища за данни, със смес от PostgreSQL и NoSQL бази данни като MongoDB или Couchbase база данни. REST API би помогнал на приложенията да изпращат данните към различни хранилища за данни. С PostgreSQL, поддържащ JSON, тази сложност в архитектурата може да бъде избегната, като се избере само едно хранилище за данни.
- JSON данните в PostgreSQL могат да бъдат запитвани и индексирани, което прави невероятна производителност и мащабируемост
- JSONB тип данни е най-предпочитаната опция, тъй като е добър при съхранение и производителност. Напълно поддържа ПЪЛНО ТЪРСЕНЕ и Индексиране. Дава добро представяне
- Използвайте тип данни JSON само ако искате да съхранявате JSON низове като JSON и не извършвате много сложни текстови търсения
- Най-голямото предимство на наличието на JSON в PostgreSQL е, че търсенето може да се извърши с SQLs
- Ефективността на JSON търсенето в PostgreSQL е наравно с най-добрите NoSQL бази данни като MongoDB