TL; DR: JSONB kann die Entwicklung von Datenbankschemata erheblich vereinfachen, ohne die Abfrageleistung zu beeintrÀchtigen.
EinfĂŒhrung
Lassen Sie uns ein klassisches Beispiel fĂŒr einen der wahrscheinlich Ă€ltesten AnwendungsfĂ€lle einer relationalen Datenbank (Datenbank) in der Welt geben: Wir haben eine EntitĂ€t und mĂŒssen bestimmte Eigenschaften (Attribute) dieser EntitĂ€t speichern. Möglicherweise verfĂŒgen jedoch nicht alle Instanzen ĂŒber denselben Satz an Eigenschaften, und in Zukunft werden möglicherweise weitere Eigenschaften hinzugefĂŒgt.
Der einfachste Weg, dieses Problem zu lösen, besteht darin, fĂŒr jeden Eigenschaftswert eine Spalte in der Datenbanktabelle zu erstellen und einfach diejenigen auszufĂŒllen, die fĂŒr eine bestimmte EntitĂ€tsinstanz benötigt werden. GroĂartig! Problem gelöst ... bis Ihre Tabelle Millionen von DatensĂ€tzen enthĂ€lt und Sie einen neuen Datensatz hinzufĂŒgen mĂŒssen.
Betrachten Sie das EAV-Muster (), kommt es recht hĂ€ufig vor. Eine Tabelle enthĂ€lt EntitĂ€ten (DatensĂ€tze), eine andere Tabelle enthĂ€lt Eigenschaftsnamen (Attribute) und eine dritte Tabelle ordnet EntitĂ€ten ihren Attributen zu und enthĂ€lt den Wert dieser Attribute fĂŒr die aktuelle EntitĂ€t. Dies gibt Ihnen die Möglichkeit, unterschiedliche EigenschaftensĂ€tze fĂŒr unterschiedliche Objekte zu verwenden und Eigenschaften im Handumdrehen hinzuzufĂŒgen, ohne die Datenbankstruktur zu Ă€ndern.
Allerdings wĂŒrde ich diesen Beitrag nicht schreiben, wenn der EVA-Ansatz nicht einige Nachteile hĂ€tte. Um beispielsweise eine oder mehrere EntitĂ€ten mit jeweils 1 Attribut zu erhalten, sind in der Abfrage zwei Joins erforderlich: Der erste ist ein Join mit der Attributtabelle, der zweite ist ein Join mit der Wertetabelle. Wenn eine EntitĂ€t 2 Attribute hat, sind 2 Joins erforderlich! DarĂŒber hinaus werden alle Attribute normalerweise als Zeichenfolgen gespeichert, was zu einer Typumwandlung sowohl fĂŒr das Ergebnis als auch fĂŒr die WHERE-Klausel fĂŒhrt. Wenn Sie viele Abfragen schreiben, ist dies im Hinblick auf den Ressourcenverbrauch ziemlich verschwenderisch.
Trotz dieser offensichtlichen MÀngel wird EAV seit langem zur Lösung dieser Art von Problemen eingesetzt. Das waren unvermeidliche MÀngel und es gab einfach keine bessere Alternative.
Doch dann tauchte in PostgreSQL eine neue âTechnologieâ auf ...
Ab PostgreSQL 9.4 wurde der Datentyp JSONB zum Speichern von JSON-BinĂ€rdaten hinzugefĂŒgt. Obwohl das Speichern von JSON in diesem Format normalerweise etwas mehr Platz und Zeit in Anspruch nimmt als das Speichern von reinem Text-JSON, ist die AusfĂŒhrung von VorgĂ€ngen damit viel schneller. JSONB unterstĂŒtzt auch die Indizierung, wodurch Abfragen noch schneller werden.
Mit dem JSONB-Datentyp können wir das umstĂ€ndliche EAV-Muster ersetzen, indem wir unserer EntitĂ€tstabelle nur eine JSONB-Spalte hinzufĂŒgen, was den Datenbankentwurf erheblich vereinfacht. Aber viele argumentieren, dass dies mit einem RĂŒckgang der ProduktivitĂ€t einhergehen sollte... Deshalb habe ich diesen Artikel geschrieben.
Einrichten einer Testdatenbank
FĂŒr diesen Vergleich habe ich die Datenbank auf einer Neuinstallation von PostgreSQL 9.5 mit dem 80-Dollar-Build erstellt Ubuntu 14.04 Nach der Konfiguration einiger Parameter in postgresql.conf habe ich Folgendes ausgefĂŒhrt: Skript mit psql. Zur Darstellung der Daten im EAV-Format wurden folgende Tabellen erstellt:
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
);
Unten finden Sie eine Tabelle, in der dieselben Daten gespeichert werden, jedoch mit Attributen in einer Spalte vom Typ JSONB â immobilien.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Sieht doch viel einfacher aus, oder? Dann wurde es zu den EntitĂ€tstabellen hinzugefĂŒgt (Einheit & entity_jsonb) 10 Millionen DatensĂ€tze, und dementsprechend wurde die Tabelle mit den gleichen Daten unter Verwendung des EAV-Musters und des Ansatzes mit einer JSONB-Spalte gefĂŒllt - entity_jsonb.properties. Somit haben wir im gesamten Satz von Eigenschaften mehrere unterschiedliche Datentypen erhalten. Beispieldaten:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}Jetzt haben wir fĂŒr beide Optionen die gleichen Daten. Beginnen wir mit dem Vergleich der Implementierungen bei der Arbeit!
Vereinfachen Sie Ihr Design
Es wurde bereits erwĂ€hnt, dass das Datenbankdesign stark vereinfacht wurde: eine Tabelle, indem eine JSONB-Spalte fĂŒr Eigenschaften verwendet wurde, anstatt drei Tabellen fĂŒr EAV zu verwenden. Doch wie spiegelt sich das in den Anfragen wider? Das Aktualisieren einer EntitĂ€tseigenschaft sieht folgendermaĂen aus:
-- 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;
Wie Sie sehen, sieht die letzte Anfrage nicht einfacher aus. Um den Wert einer Eigenschaft in einem JSONB-Objekt zu aktualisieren, mĂŒssen wir die Funktion verwenden und sollte unseren neuen Wert als JSONB-Objekt ĂŒbergeben. Wir mĂŒssen jedoch keine Kennung im Voraus kennen. Wenn wir uns das EAV-Beispiel ansehen, mĂŒssen wir sowohl die âEntity_IDâ als auch die âEntity_Attribute_IDâ kennen, um die Aktualisierung durchfĂŒhren zu können. Wenn Sie eine Eigenschaft in einer JSONB-Spalte basierend auf dem Objektnamen aktualisieren möchten, geschieht dies alles in einer einfachen Zeile.
WÀhlen wir nun die gerade aktualisierte EntitÀt anhand ihrer neuen Farbe aus:
-- 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';
Ich denke, wir können uns darauf einigen, dass der zweite kĂŒrzer (kein Join!) und daher besser lesbar ist. JSONB gewinnt hier! Wir verwenden den JSON->>-Operator, um die Farbe als Textwert aus einem JSONB-Objekt abzurufen. Es gibt auch eine zweite Möglichkeit, mit dem @>-Operator das gleiche Ergebnis im JSONB-Modell zu erzielen:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Das ist etwas komplizierter: Wir prĂŒfen, ob das JSON-Objekt in seiner Eigenschaftenspalte ein Objekt enthĂ€lt, das sich rechts vom @>-Operator befindet. Weniger lesbar, produktiver (siehe unten).
Machen wir die Verwendung von JSONB noch einfacher, wenn Sie mehrere Eigenschaften gleichzeitig auswĂ€hlen mĂŒssen. Hier kommt der JSONB-Ansatz wirklich ins Spiel: Wir wĂ€hlen einfach Eigenschaften als zusĂ€tzliche Spalten in unserem Ergebnissatz aus, ohne dass Joins erforderlich sind:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
Mit EAV benötigen Sie zwei Joins fĂŒr jede Eigenschaft, die Sie abfragen möchten. Meiner Meinung nach zeigen die oben genannten Abfragen eine groĂe Vereinfachung im Datenbankdesign. Weitere Beispiele zum Schreiben von JSONB-Abfragen finden Sie auch in Post.
Jetzt ist es an der Zeit, ĂŒber Leistung zu sprechen.
Leistung
Um die Leistung zu vergleichen, habe ich verwendet in Abfragen, um die AusfĂŒhrungszeit zu berechnen. Jede Abfrage wurde mindestens dreimal ausgefĂŒhrt, da der Abfrageplaner beim ersten Mal lĂ€nger benötigt. Zuerst habe ich die Abfragen ohne Indizes ausgefĂŒhrt. Dies war offensichtlich ein Vorteil von JSONB, da die fĂŒr EAV erforderlichen Joins keine Indizes verwenden konnten (FremdschlĂŒsselfelder wurden nicht indiziert). Danach habe ich einen Index fĂŒr die beiden FremdschlĂŒsselspalten der EAV-Wertetabelle sowie einen Index erstellt fĂŒr eine JSONB-Spalte.
Die Datenaktualisierung zeigte die folgenden zeitlichen Ergebnisse (in ms). Beachten Sie, dass die Skala logarithmisch ist:

Wir sehen, dass JSONB aus dem oben genannten Grund viel (> 50000-x) schneller als EAV ist, wenn Sie keine Indizes verwenden. Wenn wir Spalten mit PrimĂ€rschlĂŒsseln indizieren, verschwindet der Unterschied fast, aber JSONB ist immer noch 1,3-mal schneller als EAV. Beachten Sie, dass der Index der JSONB-Spalte hier keine Auswirkung hat, da wir die Eigenschaftsspalte nicht in den Bewertungskriterien verwenden.
FĂŒr die Auswahl von Daten basierend auf dem Immobilienwert erhalten wir folgende Ergebnisse (NormalmaĂstab):

Sie können feststellen, dass JSONB wiederum schneller arbeitet als EAV ohne Indizes, bei EAV mit Indizes jedoch immer noch schneller als JSONB. Aber dann sah ich, dass die Zeiten fĂŒr JSONB-Abfragen gleich waren, was mich zu der Tatsache fĂŒhrte, dass GIN-Indizes nicht funktionieren. Wenn Sie einen GIN-Index fĂŒr eine Spalte mit ausgefĂŒllten Eigenschaften verwenden, wird dieser offenbar nur wirksam, wenn Sie den Include-Operator @> verwenden. Ich habe dies in einem neuen Test verwendet und es hatte einen enormen Einfluss auf die Zeit: nur 0,153 ms! Dies ist 15000-mal schneller als EAV und 25000-mal schneller als der ->>-Operator.
Ich denke, es war schnell genug!
GröĂe der Datenbanktabelle
Vergleichen wir die TabellengröĂen fĂŒr beide AnsĂ€tze. In psql können wir mit dem Befehl die GröĂe aller Tabellen und Indizes anzeigen dti+

FĂŒr den EAV-Ansatz betragen die TabellengröĂen etwa 3068 MB und die Indizes bis zu 3427 MB, also insgesamt 6,43 GB. Der JSONB-Ansatz verwendet 1817 MB fĂŒr die Tabelle und 318 MB fĂŒr die Indizes, also 2,08 GB. Es fĂ€llt dreimal weniger aus! Diese Tatsache hat mich ein wenig ĂŒberrascht, da wir Eigenschaftsnamen in jedem JSONB-Objekt speichern.
Dennoch sprechen die Zahlen fĂŒr sich: In EAV speichern wir 2 ganzzahlige FremdschlĂŒssel pro Attributwert, was zu 8 Bytes zusĂ€tzlicher Daten fĂŒhrt. DarĂŒber hinaus speichert EAV alle Eigenschaftswerte als Text, wĂ€hrend JSONB nach Möglichkeit intern numerische und boolesche Werte verwendet, was zu einem geringeren Platzbedarf fĂŒhrt.
Ergebnisse
Insgesamt denke ich, dass das Speichern von EntitĂ€tseigenschaften im JSONB-Format das Entwerfen und Verwalten Ihrer Datenbank erheblich vereinfachen kann. Wenn Sie viele Abfragen ausfĂŒhren, funktioniert es tatsĂ€chlich effizienter, alles in derselben Tabelle wie die EntitĂ€t zu belassen. Und die Tatsache, dass dadurch die Interaktion zwischen Daten vereinfacht wird, ist bereits ein Pluspunkt, aber die resultierende Datenbank ist dreimal kleiner.
Aufgrund der durchgefĂŒhrten Tests können wir auĂerdem den Schluss ziehen, dass der Leistungsverlust sehr unbedeutend ist. In einigen FĂ€llen ist JSONB sogar schneller als EAV und damit noch besser. Dieser Benchmark deckt jedoch natĂŒrlich nicht alle Aspekte ab (z. B. EntitĂ€ten mit einer sehr groĂen Anzahl von Eigenschaften, eine deutliche Erhöhung der Anzahl der Eigenschaften vorhandener Daten, ...), also haben Sie VorschlĂ€ge, wie Sie diese verbessern können , hinterlassen Sie es gerne in den Kommentaren!
Source: habr.com
