Kontext
Ich entwerfe eine Datenbank (unter PostgreSQL 9.6), in der Daten aus einer verteilten Anwendung gespeichert werden. Aufgrund der Verteilung der Anwendung kann ich SERIAL
aufgrund möglicher Race-Bedingungen keine Auto-Increment-Ganzzahlen ( ) als Primärschlüssel verwenden.
Die natürliche Lösung besteht darin, eine UUID oder eine global eindeutige Kennung zu verwenden. Postgres wird mit einem eingebauten UUID
Typ geliefert , der perfekt passt.
Das Problem, das ich mit der UUID habe, hängt mit dem Debuggen zusammen: Es handelt sich um eine nicht menschenfreundliche Zeichenfolge. Der Bezeichner ff53e96d-5fd7-4450-bc99-111b91875ec5
sagt mir nichts, ACC-f8kJd9xKCd
obwohl er nicht garantiert eindeutig ist, sagt er mir, dass ich es mit einem ACC
Objekt zu tun habe .
Aus Programmiersicht ist es üblich, Anwendungsabfragen zu debuggen, die mehrere verschiedene Objekte betreffen. Angenommen, der Programmierer sucht fälschlicherweise nach einem ACC
(Konto-) Objekt am ORD
(Bestell-) Tisch. Mit einer von Menschen lesbaren Kennung erkennt der Programmierer das Problem sofort, während er mithilfe von UUIDs einige Zeit damit verbringt, herauszufinden, was falsch war.
Ich brauche nicht die "garantierte" Eindeutigkeit von UUIDs; Ich kann zur Erzeugung von Schlüsseln ohne Konflikte etwas Platz brauchen, aber UUID ist übertrieben. Im schlimmsten Fall wäre eine Kollision nicht das Ende der Welt (die Datenbank lehnt sie ab und die Anwendung kann sich erholen). In Anbetracht der Nachteile wäre eine kleinere, aber menschenfreundliche Kennung die ideale Lösung für meinen Anwendungsfall.
Anwendungsobjekte identifizieren
Der Bezeichner, den ich erstellt habe, hat das folgende Format:, {domain}-{string}
wobei er {domain}
durch die Objektdomäne (Konto, Bestellung, Produkt) ersetzt wird und {string}
eine zufällig generierte Zeichenfolge ist. In einigen Fällen kann es sogar sinnvoll sein, ein {sub-domain}
vor dem Zufallsstring einzufügen . Ignorieren wir die Länge von {domain}
und, {string}
um die Einzigartigkeit zu gewährleisten.
Das Format kann eine feste Größe haben, wenn es die Indizierungs- / Abfrageleistung unterstützt.
Das Problem
Wissend, dass:
- Ich möchte Primärschlüssel mit einem Format wie haben
ACC-f8kJd9xKCd
. - Diese Primärschlüssel sind Teil mehrerer Tabellen.
- Alle diese Schlüssel werden für mehrere Joins / Beziehungen in einer 6NF-Datenbank verwendet.
- Die meisten Tabellen haben eine mittlere bis große Größe (durchschnittlich ~ 1 Million Zeilen; die größten mit ~ 100 Millionen Zeilen).
Was die Leistung angeht, wie kann dieser Schlüssel am besten gespeichert werden?
Im Folgenden sind vier mögliche Lösungen aufgeführt. Da ich jedoch wenig Erfahrung mit Datenbanken habe, bin ich mir nicht sicher, welche (falls vorhanden) die beste ist.
Überlegte Lösungen
1. Speichern als String ( VARCHAR
)
(Postgres macht keinen Unterschied zwischen CHAR(n)
und VARCHAR(n)
, also ignoriere ich CHAR
).
Nach einigen Recherchen habe ich herausgefunden, dass der Vergleich von Zeichenfolgen mit VARCHAR
, insbesondere bei Verknüpfungsoperationen, langsamer ist als die Verwendung INTEGER
. Das macht Sinn, aber ist es etwas, worüber ich mich in dieser Größenordnung sorgen sollte?
2. Speichern als binär ( bytea
)
Im Gegensatz zu Postgres hat MySQL keinen nativen UUID
Typ. In mehreren Beiträgen wird das Speichern einer UUID mithilfe eines 16-Byte- BINARY
Felds anstelle eines 36-Byte - Felds erläutert VARCHAR
. Diese Posts brachten mich auf die Idee, den Schlüssel als Binärdatei ( bytea
auf Postgres) zu speichern .
Das spart Größe, aber ich bin mehr auf Leistung bedacht. Ich hatte wenig Glück, eine Erklärung zu finden, bei der der Vergleich schneller ist: binäre oder String-Vergleiche. Ich glaube, binäre Vergleiche sind schneller. Wenn dies der Fall ist, bytea
ist dies wahrscheinlich besser als VARCHAR
, obwohl der Programmierer die Daten jetzt jedes Mal codieren / decodieren muss.
Ich könnte mich irren, aber ich denke beides bytea
und VARCHAR
werde (Gleichheits-) Byte für Byte (oder Zeichen für Zeichen) vergleichen. Gibt es eine Möglichkeit, diesen schrittweisen Vergleich zu "überspringen" und einfach "das Ganze" zu vergleichen? (Ich denke nicht, aber es kostet keine Überprüfung).
Ich denke, Speichern als bytea
ist die beste Lösung, aber ich frage mich, ob es noch andere Alternativen gibt, die ich ignoriere. Die gleiche Besorgnis, die ich zu Lösung 1 geäußert habe, gilt auch: Reicht der Aufwand für Vergleiche aus, um den ich mich sorgen sollte?
"Kreative Lösungen
Ich habe zwei sehr "kreative" Lösungen gefunden, die funktionieren könnten. Ich bin mir nur unsicher, in welchem Umfang (dh wenn ich Probleme hätte, sie auf mehr als ein paar tausend Zeilen in einer Tabelle zu skalieren).
3. Speichern als, UUID
jedoch mit einem "Etikett" versehen
Der Hauptgrund, keine UUIDs zu verwenden, besteht darin, dass Programmierer die Anwendung besser debuggen können. Was aber, wenn wir beide verwenden können: Die Datenbank speichert alle Schlüssel nur als UUID
s, umschließt jedoch das Objekt, bevor / nachdem Abfragen durchgeführt wurden.
Der Programmierer fragt beispielsweise nach ACC-{UUID}
, die Datenbank ignoriert den ACC-
Teil, ruft die Ergebnisse ab und gibt sie alle als zurück {domain}-{UUID}
.
Möglicherweise wäre dies mit etwas Hackerei mit gespeicherten Prozeduren oder Funktionen möglich, aber einige Fragen kommen in den Sinn:
- Ist dies (Entfernen / Hinzufügen der Domäne bei jeder Abfrage) ein erheblicher Aufwand?
- Ist das überhaupt möglich?
Ich habe noch nie gespeicherte Prozeduren oder Funktionen verwendet, daher bin ich mir nicht sicher, ob dies überhaupt möglich ist. Kann jemand Licht ins Dunkel bringen? Wenn ich eine transparente Ebene zwischen dem Programmierer und den gespeicherten Daten einfügen kann, scheint dies eine perfekte Lösung zu sein.
4. (Mein Favorit) Als IPv6 speichern cidr
Ja, du hast es richtig gelesen. Es stellt sich heraus, dass das IPv6-Adressformat mein Problem perfekt löst .
- Ich kann in den ersten Oktetten Domänen und Unterdomänen hinzufügen und die übrigen als Zufallszeichenfolge verwenden.
- Die Kollisionswahrscheinlichkeiten sind in Ordnung. (Ich würde zwar nicht 2 ^ 128 verwenden, aber es ist immer noch OK.)
- Gleichstellungsvergleiche werden (hoffentlich) optimiert, sodass ich möglicherweise eine bessere Leistung erhalte als nur die Verwendung
bytea
. - Ich kann tatsächlich einige interessante Vergleiche durchführen,
contains
je nachdem, wie die Domänen und ihre Hierarchie dargestellt werden.
Angenommen, ich verwende Code 0000
, um die Domäne "Produkte" darzustellen. Schlüssel 0000:0db8:85a3:0000:0000:8a2e:0370:7334
würde das Produkt darstellen 0db8:85a3:0000:0000:8a2e:0370:7334
.
Die Hauptfrage ist hier: Gibt es im Vergleich zu Datentypen bytea
einen Hauptvorteil oder einen Hauptnachteil cidr
?
varchar
unter vielen anderen Problemen. Ich wusste nichts über die Domains von pg, was sehr interessant ist. Ich sehe, dass Domänen verwendet werden, um zu überprüfen, ob eine bestimmte Abfrage das richtige Objekt verwendet, aber es würde immer noch einen nicht ganzzahligen Index voraussetzen. Ich bin nicht sicher, ob es eine "sichere" Verwendungsweise serial
gibt (ohne einen Sperrschritt).
varchar
. Überlegen Sie, ob Sie einen FK
integer
Typ festlegen und eine Nachschlagetabelle hinzufügen möchten. Auf diese Weise können Sie sowohl die menschliche Lesbarkeit als auch den Composite PK
vor Einfüge- / Aktualisierungsanomalien schützen (eine nicht vorhandene Domain einrichten).
text
ist vorzuziehen varchar
. Schauen Sie sich depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text und postgresql.org/docs/current/static/datatype-character.html
ACC-f8kJd9xKCd
. ”← Das scheint ein Job für den guten alten zusammengesetzten PRIMARY KEY zu sein .