TL; DR: JSONB kan avsevÀrt förenkla utvecklingen av databasschema utan att offra frÄgeprestanda.
Inledning
LÄt oss ge ett klassiskt exempel pÄ förmodligen ett av de Àldsta anvÀndningsfallen i vÀrlden av en relationsdatabas (databas): vi har en entitet, och vi mÄste spara vissa egenskaper (attribut) för denna entitet. Men alla instanser kanske inte har samma uppsÀttning egenskaper, och fler egenskaper kan lÀggas till i framtiden.
Det enklaste sÀttet att lösa detta problem Àr att skapa en kolumn i databastabellen för varje egenskapsvÀrde och helt enkelt fylla i de som behövs för en specifik entitetsinstans. Bra! Problem löst... tills din tabell innehÄller miljontals poster och du behöver lÀgga till en ny post.
TÀnk pÄ EAV-mönstret (), förekommer det ganska ofta. En tabell innehÄller entiteter (poster), en annan tabell innehÄller egenskapsnamn (attribut) och en tredje tabell associerar enheter med deras attribut och innehÄller vÀrdet av dessa attribut för den aktuella enheten. Detta ger dig möjligheten att ha olika uppsÀttningar egenskaper för olika objekt, och Àven lÀgga till egenskaper i farten utan att Àndra databasstrukturen.
Jag skulle dock inte skriva det hÀr inlÀgget om det inte fanns nÄgra nackdelar med EVA-metoden. SÄ, till exempel, för att fÄ en eller flera entiteter som har 1 attribut vardera, krÀvs 2 joins i frÄgan: den första Àr en join med attributtabellen, den andra Àr en join med vÀrdetabellen. Om en entitet har 2 attribut behövs 4 kopplingar! Dessutom lagras alla attribut vanligtvis som strÀngar, vilket resulterar i typcasting för bÄde resultatet och WHERE-satsen. Om du skriver mÄnga frÄgor Àr detta ganska slösaktigt nÀr det gÀller resursanvÀndning.
Trots dessa uppenbara brister har EAV lÀnge anvÀnts för att lösa den hÀr typen av problem. Dessa var oundvikliga brister, och det fanns helt enkelt inget bÀttre alternativ.
Men sÄ dök en ny "teknik" upp i PostgreSQL...
FrĂ„n och med PostgreSQL 9.4 lades JSONB-datatypen till för att lagra binĂ€r JSON-data. Ăven om lagring av JSON i det hĂ€r formatet vanligtvis tar lite mer utrymme och tid Ă€n JSON med vanlig text, Ă€r det mycket snabbare att utföra operationer pĂ„ det. JSONB stöder Ă€ven indexering, vilket gör frĂ„gorna Ă€nnu snabbare.
JSONB-datatypen tillÄter oss att ersÀtta det krÄngliga EAV-mönstret genom att lÀgga till bara en JSONB-kolumn i vÄr entitetstabell, vilket avsevÀrt förenklar databasdesign. Men mÄnga hÀvdar att detta borde Ätföljas av en minskning av produktiviteten... Det var dÀrför jag skrev den hÀr artikeln.
Skapa en testdatabas
För denna jÀmförelse skapade jag databasen pÄ en ny installation av PostgreSQL 9.5 pÄ $80 build Ubuntu 14.04 Efter att ha konfigurerat nÄgra parametrar i postgresql.conf körde jag skript med psql. Följande tabeller skapades för att presentera data i EAV-form:
CREATE TABLE entity (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT
);
CREATE TABLE entity_attribute (
id SERIAL PRIMARY KEY,
name TEXT
);
CREATE TABLE entity_attribute_value (
id SERIAL PRIMARY KEY,
entity_id INT REFERENCES entity(id),
entity_attribute_id INT REFERENCES entity_attribute(id),
value TEXT
);
Nedan finns en tabell dĂ€r samma data kommer att lagras, men med attribut i en kolumn av JSONB-typ â egenskaper.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Ser mycket enklare ut, eller hur? Sedan lades det till i entitetstabellerna (enhet & entity_jsonb) 10 miljoner poster, och följaktligen fylldes tabellen med samma data med hjÀlp av EAV-mönstret och tillvÀgagÄngssÀttet med en JSONB-kolumn - entity_jsonb.properties. SÄledes fick vi flera olika datatyper bland hela uppsÀttningen av egenskaper. Exempeldata:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}SÄ nu har vi samma data för bÄda alternativen. LÄt oss börja jÀmföra implementeringar pÄ jobbet!
Förenkla din design
Det har tidigare sagts att databasdesignen förenklades kraftigt: en tabell, genom att anvÀnda en JSONB-kolumn för egenskaper, istÀllet för att anvÀnda tre tabeller för EAV. Men hur Äterspeglas detta i förfrÄgningar? Att uppdatera en enhetsegenskap ser ut sÄ hÀr:
-- EAV
UPDATE entity_attribute_value
SET value = 'blue'
WHERE entity_attribute_id = 1
AND entity_id = 120;
-- JSONB
UPDATE entity_jsonb
SET properties = jsonb_set(properties, '{"color"}', '"blue"')
WHERE id = 120;
Som du kan se ser den sista begĂ€ran inte enklare ut. För att uppdatera vĂ€rdet pĂ„ en egenskap i ett JSONB-objekt mĂ„ste vi anvĂ€nda funktionen , och bör skicka vĂ„rt nya vĂ€rde som ett JSONB-objekt. Vi behöver dock inte kĂ€nna till nĂ„gon identifierare i förvĂ€g. Om vi ââtittar pĂ„ EAV-exemplet mĂ„ste vi kĂ€nna till bĂ„de entity_id och entity_attribute_id för att kunna utföra uppdateringen. Om du vill uppdatera en egenskap i en JSONB-kolumn baserat pĂ„ objektnamnet, görs allt pĂ„ en enkel rad.
LÄt oss nu vÀlja den enhet vi just uppdaterade baserat pÄ dess nya fÀrg:
-- EAV
SELECT e.name
FROM entity e
INNER JOIN entity_attribute_value eav ON e.id = eav.entity_id
INNER JOIN entity_attribute ea ON eav.entity_attribute_id = ea.id
WHERE ea.name = 'color' AND eav.value = 'blue';
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties ->> 'color' = 'blue';
Jag tror att vi kan komma överens om att den andra Àr kortare (ingen anslutning!), och dÀrför mer lÀsbar. JSONB vinner hÀr! Vi anvÀnder operatorn JSON ->> för att fÄ fÀrgen som ett textvÀrde frÄn ett JSONB-objekt. Det finns ocksÄ ett andra sÀtt att uppnÄ samma resultat i JSONB-modellen med @>-operatorn:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Det hÀr Àr lite mer komplicerat: vi kontrollerar om JSON-objektet i egenskapskolumnen innehÄller ett objekt som Àr till höger om @>-operatorn. Mindre lÀsbar, mer produktiv (se nedan).
LÄt oss göra det Ànnu enklare att anvÀnda JSONB nÀr du behöver vÀlja flera egenskaper samtidigt. Det Àr hÀr som JSONB-metoden verkligen kommer in: vi vÀljer helt enkelt egenskaper som ytterligare kolumner i vÄr resultatuppsÀttning utan behov av kopplingar:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
Med EAV behöver du 2 anslutningar för varje fastighet du vill frÄga. Enligt min Äsikt visar ovanstÄende frÄgor en stor förenkling i databasdesign. Se fler exempel pÄ hur man skriver JSONB-frÄgor, Àven i posta.
Nu Àr det dags att prata om prestation.
ĐŃĐŸĐžĐ·ĐČĐŸĐŽĐžŃДлŃĐœĐŸŃŃŃ
För att jÀmföra prestanda anvÀnde jag i frÄgor, för att berÀkna exekveringstiden. Varje frÄga kördes minst tre gÄnger eftersom frÄgeplaneraren tar lÀngre tid första gÄngen. Först körde jag frÄgorna utan nÄgra index. Uppenbarligen var detta en fördel med JSONB, eftersom de kopplingar som krÀvs för EAV inte kunde anvÀnda index (utlÀndska nyckelfÀlt indexerades inte). Efter detta skapade jag ett index pÄ de tvÄ frÀmmande nyckelkolumnerna i EAV-vÀrdestabellen, samt ett index för en JSONB-kolumn.
Datauppdateringen visade följande resultat i termer av tid (i ms). Observera att skalan Àr logaritmisk:

Vi ser att JSONB Àr mycket (> 50000-x) snabbare Àn EAV om du inte anvÀnder index, av anledningen som anges ovan. NÀr vi indexerar kolumner med primÀrnycklar försvinner nÀstan skillnaden, men JSONB Àr fortfarande 1,3 gÄnger snabbare Àn EAV. Observera att indexet pÄ JSONB-kolumnen inte har nÄgon effekt hÀr eftersom vi inte anvÀnder egenskapskolumnen i utvÀrderingskriterierna.
För att vÀlja data baserat pÄ egenskapsvÀrde fÄr vi följande resultat (normal skala):

Du kan mÀrka att JSONB Äterigen fungerar snabbare Àn EAV utan index, men nÀr EAV med index fungerar det fortfarande snabbare Àn JSONB. Men sedan sÄg jag att tiderna för JSONB-frÄgor var desamma, detta fick mig att inse att GIN-index inte fungerar. Tydligen nÀr du anvÀnder ett GIN-index pÄ en kolumn med ifyllda egenskaper, trÀder det bara i kraft nÀr du anvÀnder include-operatorn @>. Jag anvÀnde detta i ett nytt test och det hade en enorm inverkan pÄ tiden: bara 0,153ms! Detta Àr 15000 25000 gÄnger snabbare Àn EAV och XNUMX XNUMX gÄnger snabbare Àn ->> operatören.
Jag tycker det var tillrÀckligt snabbt!
Databastabellstorlek
LÄt oss jÀmföra tabellstorlekarna för bÄda tillvÀgagÄngssÀtten. I psql kan vi visa storleken pÄ alla tabeller och index med kommandot dti+

För EAV-metoden Àr tabellstorlekarna cirka 3068 MB och indexerar upp till 3427 MB för totalt 6,43 GB. JSONB-metoden anvÀnder 1817 MB för tabellen och 318 MB för indexen, vilket Àr 2,08 GB. Det blir 3 gÄnger mindre! Detta faktum förvÄnade mig lite eftersom vi lagrar fastighetsnamn i varje JSONB-objekt.
Men ÀndÄ talar siffrorna för sig sjÀlva: i EAV lagrar vi 2 heltals frÀmmande nycklar per attributvÀrde, vilket resulterar i 8 byte med ytterligare data. Dessutom lagrar EAV alla egenskapsvÀrden som text, medan JSONB kommer att anvÀnda numeriska och booleska vÀrden internt dÀr det Àr möjligt, vilket resulterar i ett mindre fotavtryck.
Resultat av
Sammantaget tror jag att att spara entitetsegenskaper i JSONB-format kan göra design och underhÄll av din databas mycket enklare. Om du kör mÄnga frÄgor kommer det att fungera mer effektivt om du hÄller allt i samma tabell som enheten. Och det faktum att detta förenklar interaktionen mellan data Àr redan ett plus, men den resulterande databasen Àr 3 gÄnger mindre i volym.
Baserat pÄ utförda tester kan vi ocksÄ dra slutsatsen att prestationsförlusterna Àr mycket obetydliga. I vissa fall Àr JSONB Ànnu snabbare Àn EAV, vilket gör den Ànnu bÀttre. Men detta riktmÀrke tÀcker naturligtvis inte alla aspekter (t.ex. enheter med ett mycket stort antal egenskaper, en betydande ökning av antalet egenskaper för befintliga data,...), sÄ om du har nÄgra förslag pÄ hur man kan förbÀttra dem , lÀmna gÀrna i kommentarerna!
KĂ€lla: will.com
