Vad är JSON?
JSON står för "JavaScript Object Notation" som är en typ av dataformat som populärt används av webbapplikationer. Detta innebär att data skulle överföras mellan webbapplikationer och servrar i ett sådant format. JSON introducerades som ett alternativ till XML-formatet. På "gamla goda dagar" användes data för att överföras i XML-format som är en tung datatyp jämfört med JSON. Nedan är ett exempel på JSON-formaterad sträng:
{ "ID":"001","name": "Ven", "Country": "Australia", "city": "Sydney", "Job Title":"Database Consultant"}
En JSON-sträng kan innehålla ett annat JSON-objekt i sig själv som visas nedan:
{ "ID":"001", "name": "Ven", "Job Title":"Database Consultant", "Location":{"Suburb":"Dee Why","city": "Sydney","State":"NSW","Country": "Australia"}}
Moderna webb- och mobilapplikationer genererar mestadels data i JSON-format, även kallade "JSON-bytes", som plockas upp av applikationsservrarna och skickas till databasen. JSON-byten bearbetas i sin tur, delas upp i separata kolumnvärden och infogas i en RDBMS-tabell.
Exempel:
{ "ID":"001","name": "Ven", "Country": "Australia", "city": "Sydney", "Job Title":"Database Consultant"}
Ovan JSON-data konverteras till en SQL som nedan...
Insert into test (id, name, country,city,job_title) values (001,'Ven','Australia','Sydney','Database Consultant');
När det gäller att lagra och bearbeta JSON-data finns det olika NoSQL-databaser som stöder det och den mest populära är MongoDB. När det gäller RDBMS-databaser, fram till nyligen, behandlades JSON-strängar som normal text och det fanns inga datatyper som specifikt känner igen, lagrar eller bearbetar JSON-formatsträngar. PostgreSQL, den mest populära RDBMS-databasen med öppen källkod, har kommit med JSON-datatyp som visade sig vara mycket fördelaktig för prestanda, funktionalitet och skalbarhet när det gäller att hantera JSON-data.
PostgreSQL + JSON
PostgreSQL-databas har blivit mer och mer populär ända sedan JSON-datatypen introducerades. Faktum är att PostgreSQL har överträffat MongoDB när det gäller att bearbeta en stor mängd JSON-data. Applikationerna kan lagra JSON-strängar i PostgreSQL-databasen i standard JSON-format. Utvecklare behöver bara berätta för applikationen att skicka över JSON-strängarna till databasen som en json-datatyp och hämta tillbaka i JSON-formatet. Lagring av JSON-sträng i JSON-datatyp har flera fördelar jämfört med att lagra densamma i TEXT-datatyp. JSON-datatyp kan endast acceptera giltiga JSON-formaterade strängar, om strängen inte är i korrekt JSON-format genereras ett fel. JSON-datatyp hjälper applikationen att utföra effektiva och indexbaserade sökningar som vi kommer att se i detalj inom kort.
JSON-datatypen introducerades i PostgreSQL-9.2-posten, där betydande förbättringar gjordes. Det stora tillägget kom upp i PostgreSQL-9.4 med tillägget av JSONB-datatyp. JSONB är en avancerad version av JSON-datatypen som lagrar JSON-data i binärt format. Detta är den stora förbättringen som gjorde stor skillnad för hur JSON-data söktes och bearbetades i PostgreSQL. Låt oss ta en detaljerad titt på fördelarna med JSON-datatyper.
JSON- och JSONB-datatyper
JSON-datatyp lagrar json-formaterade strängar som en text som inte är särskilt kraftfull och inte stöder många JSON-relaterade funktioner som används för sökningar. Den stöder endast traditionell B-TREE-indexering och stöder inte andra indextyper som är absolut nödvändiga för snabbare och effektivare sökoperationer över JSON-data.
JSONB, den avancerade versionen av JSON-datatypen, rekommenderas starkt för lagring och bearbetning av JSON-dokument. Den stöder ett brett utbud av json-operatörer och har många fördelar jämfört med JSON, som att lagra JSON-formaterade strängar i binärt format och stödja JSON-funktioner och indexering för att utföra effektiva sökningar.
Låt oss titta på skillnaderna.
JSON | JSONB | |
---|---|---|
1 | Ganska mycket som en TEXT-datatyp som endast lagrar ett giltigt JSON-dokument. | Lagrar JSON-dokumenten i binärt format. |
2 | Lagrar JSON-dokumenten som de är inklusive blanksteg. | Trimmar av vita utrymmen och lagrar i ett format som främjar snabbare och effektivare sökningar |
3 | Stöder inte indexering av FULLTEXT-SEARCH | Stöder indexering av FULLTEXT-SEARCH |
4 | Stöder inte många olika JSON-funktioner och -operatörer | Stöder alla JSON-funktioner och operatörer |
Exempel för #4 listad ovan
JSON
Nedan finns en tabell med JSON-datatyp
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)
Stöder inte traditionella JSON-operatörer (som "@>" eller "#>"). Fulltextsökning genom JSON-data görs med "@>" eller "#>" i en SQL som inte stöds av JSON-datatypen
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
Nedan finns en tabell med JSONB-datatyp
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)
Stöder FULLTEXTSÖKNING genom JSON-data med operatorer (som "@>")
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)
Ladda ner Whitepaper Today PostgreSQL Management &Automation med ClusterControlLäs om vad du behöver veta för att distribuera, övervaka, hantera och skala PostgreSQLDladda Whitepaper Hur man frågar efter JSON-data
Låt oss ta en titt på några PostgreSQL JSON-funktioner relaterade till dataoperationer. Nedan är hur JSON-data ser ut i en tabell. Kolumnen "order_details" är av typen 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)
Välj alla artikelkoder inklusive deras leveransdatum
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)
Få item_code, kvantitet och pris på alla beställningar som kommit med flyg
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-operatorerna "->" och "->>" används för urval och jämförelser i SQL-frågan. Operatorn "->" returnerar JSON-objektfältet som ett fält inom citattecken och operatorn "->>" returnerar JSON-objektfältet som TEXT. Ovanstående två SQL:er är exempel på att visa JSON-fältvärden som de är. Nedan är ett exempel på att extrahera JSON-fältet i TEXT-formuläret.
Nedan är ett exempel på att hämta JSON-fältet i form av 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)
Det finns en annan operatör som heter "#>" som används för att fråga datadelen av ett JSON-element som i sin tur är en del av en JSON-sträng. Låt oss titta på ett exempel.
Nedan är data i tabellen.
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"}}
Jag vill se alla detaljer med "State" "NSW" och "State" är JSON-objektnyckeln som är en del av nyckeln "Location". Nedan är hur du frågar detsamma.
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)
Aritmetiska operationer kan utföras på JSON-data. Typcasting behövs eftersom datadelen av JSON-kolumnen är 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)
Bortsett från allt ovanstående kan följande operationer också utföras på JSON med SQLs inklusive JOINs
- Sortering av data med hjälp av ORDER BY-satsen
- Aggregation med aggregerade funktioner som SUM, AVG, MIN, MAX etc.
- Gruppera data med hjälp av GROUP BY-satsen
Vad sägs om prestanda?
Data i JSON-kolumner kommer att vara av text och baserat på datastorleken kan prestandaproblem förväntas. Sökningar genom JSON-data kan ta tid och beräkningskraft vilket resulterar i långsamma svar på applikationen/apparna. Det är absolut nödvändigt för DBA:er att säkerställa att SQL:er som träffar JSON-kolumnerna svarar tillräckligt snabbt och ger bra prestanda. Eftersom dataextraheringen görs via SQL, är alternativet DBA:erna skulle leta efter möjligheten att indexera och ja, JSON-datatyper stöder indexeringsalternativ.
Låt oss ta en titt på indexeringsalternativen som JSON ger oss.
Indexering av JSONB
JSONB-datatypen stöder indexering av FULLTEXT-SEARCH. Detta är den viktigaste förmågan hos JSONB som DBA:er kommer att se fram emot när de använder JSONB-datatyper. Ett normalt index på en JSON-objektnyckel kanske inte hjälper när du använder JSON-specifika operatorer i sökfrågorna. Nedan finns en TEXTSEARCH-fråga som används för en HELA TABELL-SCANNING
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 stöder FULL-TEXT-SEARCH Indextypen GIN som hjälper frågor som ovan.
Låt mig skapa ett GIN-index och se om det hjälper
dbt3=# create index od_gin_idx on products using gin(order_details jsonb_path_ops);
CREATE INDEX
Om du kan observera nedan, hämtar frågan GIN-indexet
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)
Och ett B-TREE-index istället för GIN skulle INTE hjälpa
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))
Du kan se nedan, frågan föredrar 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)
Vad är GIN-index?
GIN står för Generalized Inverted Index. Kärnan i GIN Index är att påskynda fulltextsökningar. När du söker baserat på specifika nycklar eller element i en TEXT eller ett dokument, är GIN Index rätt väg att gå. GIN Index lagrar "Nyckel" (eller ett element eller ett värde) och "positionslista"-paren. Positionslistan är nyckelns rad-ID. Detta innebär att om "nyckeln" förekommer på flera ställen i dokumentet, lagrar GIN-index nyckeln endast en gång tillsammans med dess position för händelser, vilket inte bara håller GIN-indexet kompakt i storlek och också hjälper till att påskynda sökningarna på ett bra sätt. sätt. Detta är förbättringen i Postgres-9.4.
Utmaningar med GIN-index
Beroende på datas komplexitet kan det vara dyrt att upprätthålla GIN-index. Skapandet av GIN-index kräver tid och resurser eftersom Indexet måste söka igenom hela dokumentet för att hitta nycklarna och deras rad-ID. Det kan vara ännu mer utmanande om GIN-indexet är uppsvällt. Storleken på GIN-index kan också vara mycket stor baserat på datastorlek och komplexitet.
Indexering av JSON
JSON stöder inte textsökning och index som 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
Normal indexering som B-TREE stöds av både JSON och JSONB
Ja, normala index som B-TREE Index stöds av både JSON- och JSONB-datatyper och är inte gynnsamma för textsökningsoperationer. Varje JSON-objektnyckel kan indexeras individuellt vilket verkligen skulle hjälpa ENDAST när samma objektnyckel används i WHERE-satsen.
Låt mig skapa ett B-TREE Index på JSONB och se hur det fungerar
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))
Vi har redan lärt oss ovan att ett B-TREE-index INTE är användbart för att påskynda SQL-filer som gör FULLTEXTSÖKNING på JSON-data med operatorer (som "@>") , och sådana index skulle ENDAST hjälpa till att snabba upp frågorna som t.ex. den nedan, som är typiska SQL-filer av RDBMS-typ (som inte är sökfrågor). Var och en av JSON-objektnycklarna kan indexeras individuellt, vilket skulle hjälpa frågor att snabba upp när dessa indexerade JSON-objektnycklar används WHERE-satsen.
Exemplet nedan använder frågan "l_shipmode" Object Key i WHERE-satsen och eftersom den är indexerad, frågan går för en indexskanning. Om du vill söka med en annan objektnyckel, så skulle frågan välja att göra en HELT TABELL-SCANNING.
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)
Samma fungerar med JSON-datatyp också
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))
Om du kan observera använder frågan indexet
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)
Slutsats
Här är några saker att komma ihåg när du använder PostgreSQL JSON Data...
- PostgreSQL är ett av de bästa alternativen för att lagra och bearbeta JSON-data
- Med alla kraftfulla funktioner kan PostgreSQL vara din dokumentdatabas
- Jag har sett arkitekturer där två eller flera datalager väljs, med en blandning av PostgreSQL- och NoSQL-databaser som MongoDB eller Couchbase-databas. Ett REST API skulle hjälpa applikationer att skicka data till olika datalager. Med PostgreSQL som stöder JSON kan denna komplexitet i arkitektur undvikas genom att bara välja ett datalager.
- JSON-data i PostgreSQL kan efterfrågas och indexeras vilket ger otrolig prestanda och skalbarhet
- JSONB-datatyp är det mest föredragna alternativet eftersom det är bra på lagring och prestanda. Stöder fullt ut FULLTEXTSÖKNING och indexering. Ger bra prestanda
- Använd endast JSON-datatyp om du vill lagra JSON-strängar som JSON och du inte utför mycket komplexa textsökningar
- Den största fördelen med att ha JSON i PostgreSQL är att sökningen kan utföras med SQLs
- JSON-sökprestanda i PostgreSQL har varit i nivå med de bästa NoSQL-databaserna som MongoDB