UUID-Leistung in MySQL?

82

Wir erwägen, UUID-Werte als Primärschlüssel für unsere MySQL-Datenbank zu verwenden. Die eingefügten Daten werden von Dutzenden, Hunderten oder sogar Tausenden von Remotecomputern generiert und mit einer Rate von 100-40.000 Einfügungen pro Sekunde eingefügt. Wir werden niemals Aktualisierungen vornehmen.

Die Datenbank selbst erreicht normalerweise etwa 50 Millionen Datensätze, bevor wir mit dem Auslesen von Daten beginnen. Es handelt sich also nicht um eine massive Datenbank, sondern auch nicht um eine winzige. Wir planen auch, auf InnoDB zu laufen, obwohl wir offen dafür sind, dies zu ändern, wenn es eine bessere Engine für das gibt, was wir tun.

Wir waren bereit, mit Javas Typ 4-UUID zu arbeiten, haben aber beim Testen ein seltsames Verhalten festgestellt. Zum einen speichern wir als varchar (36) und mir ist jetzt klar, dass wir besser mit binär (16) umgehen können - obwohl ich nicht sicher bin, wie viel besser es uns geht.

Die größere Frage ist: Wie stark vermasseln diese zufälligen Daten den Index, wenn wir 50 Millionen Datensätze haben? Wären wir besser dran, wenn wir zum Beispiel eine UUID vom Typ 1 verwenden würden, bei der die Bits ganz links mit einem Zeitstempel versehen wären? Oder sollten wir UUIDs komplett weglassen und Auto_Increment-Primärschlüssel in Betracht ziehen?

Ich suche nach allgemeinen Gedanken / Tipps zur Leistung verschiedener Arten von UUIDs, wenn diese als Index / Primärschlüssel in MySQL gespeichert sind. Vielen Dank!

Patrick Lightbody
quelle
2
Ein wichtiges Detail fehlt: Werden die Primärschlüssel vom Protokollierungsserver oder von den Clientcomputern selbst generiert?
1
@hop sie werden von den 10-1000 Clients generiert, die die Daten einfügen
Patrick Lightbody
Wo brauchen Sie die universelle Einzigartigkeit in Ihrem Szenario? Mein Rat ist, sich an auto_increment zu halten und ein separates Feld zu verwenden, um den Remotecomputer zu beschreiben, der die Daten sendet. Hier muss das Rad nicht neu erfunden werden.
Theodore Zographos

Antworten:

35

Eine UUID ist eine universell eindeutige ID. Es ist der universelle Teil, den Sie hier berücksichtigen sollten.

Benötigen Sie wirklich die IDs, um universell eindeutig zu sein? In diesem Fall sind UUIDs möglicherweise Ihre einzige Wahl.

Ich würde dringend empfehlen, UUIDs, wenn Sie sie verwenden, als Zahl und nicht als Zeichenfolge zu speichern. Wenn Sie über 50 Millionen Datensätze verfügen, verbessert die Einsparung von Speicherplatz Ihre Leistung (obwohl ich nicht sagen kann, um wie viel).

Wenn Ihre IDs nicht universell eindeutig sein müssen, können Sie meiner Meinung nach nicht viel besser als nur auto_increment, was garantiert, dass IDs innerhalb einer Tabelle eindeutig sind (da der Wert jedes Mal erhöht wird).

Dancrumb
quelle
2
Interessanter Punkt; Dies würde die Erzeugung der Schlüssel parallelisieren. Ich glaube, dass dies die Leistung der Schlüsselgenerierung steigern würde. Sie wählen jedoch INSERT-Leistung gegenüber SELECT-Leistung, wenn Sie VARCHAR zum Speichern der UUID verwenden. Sie sollten auf jeden Fall VARBINARY zum Speichern wählen, um die SELECT-Leistung sicherzustellen. Der zusätzliche Schritt kann sich auf die INSERT-Leistung auswirken, aber Sie werden mit der SELECT-Leistungsverbesserung bezahlt.
Dancrumb
12
Am Ende haben wir ein Benchmarking für reale Daten durchgeführt und GUIDs ohne Schlüssel waren ziemlich schnell, GUIDs mit Schlüsseln waren schrecklich (selbst wenn sie als BINARY gespeichert wurden) und int w / AUTO_COMPLETE war am schnellsten. Ich denke, in unserem Fall haben wir tatsächlich den Wald von den Bäumen vermisst, da die Sequenzgenerierung im Vergleich zu den Kosten für das Speichern von mehr Daten + einem wirklich beschissenen BTREE aufgrund der Zufälligkeit der GUIDs
Patrick Lightbody
1
Als Zahl speichern heißt im Binärformat speichern? Aber das Binärformat ist für den Menschen nicht lesbar. Es ist langsam, weil große Bytes des UUID-Primärschlüssels? Wenn ja, könnte ich die automatische Inkrementierung mit einer anderen Spalte für uuid speichern. Dann wird die Leistung nicht leiden. Habe ich recht?
Chamnap
4
Genau genommen ist UUID universell einzigartig, was bedeutet, dass es nirgendwo anders auf der Welt erscheinen wird. Sie benötigen dies nur, wenn Sie Ihre Daten öffentlich teilen. Was das Speichern einer UUID als Zahl betrifft, meine ich nicht das binaryFormat. Ich meine als 128-Bit-Zahl und nicht als 288-Bit-Zeichenfolge. Zum Beispiel ist das Wort "Hallo" in ASCII 68 65 6C 6C 6Fdie Nummer 448,378,203,247. Das Speichern der Zeichenfolge '68656C6C6F' erfordert 10 Byte. Die Nummer 448,378,203,247 erfordert nur 5. Alles in allem können Sie nicht viel besser machen alsauto_increment
Dancrumb
1
@Chamnap: Schlagen Sie vor, dass Sie eine Stapelüberlauffrage stellen: o)
Dancrumb
77

Bei meiner Arbeit verwenden wir UUID als PKs. Was ich Ihnen aus Erfahrung sagen kann, ist, SIE NICHT als PKs zu verwenden (SQL Server übrigens).

Es ist eines dieser Dinge, dass es in Ordnung ist, wenn Sie weniger als 1000 Datensätze haben, aber wenn Sie Millionen haben, ist es das Schlimmste, was Sie tun können. Warum? Da die UUID nicht sequentiell sind, muss MSSQL jedes Mal, wenn ein neuer Datensatz eingefügt wird, auf die richtige Seite schauen, um den Datensatz einzufügen, und dann den Datensatz einfügen. Die wirklich hässliche Konsequenz daraus ist, dass die Seiten alle unterschiedlich groß und fragmentiert sind. Jetzt müssen wir regelmäßig eine De-Fragmentierung durchführen.

Wenn Sie eine automatische Inkrementierung verwenden, wechselt MSSQL immer zur letzten Seite, und Sie erhalten (theoretisch) gleich große Seiten, sodass die Leistung bei der Auswahl dieser Datensätze viel besser ist (auch, weil die INSERTs die Tabelle / Seite nicht blockieren so lange).

Der große Vorteil der Verwendung von UUID als PKs besteht jedoch darin, dass bei Zusammenführung von DB-Clustern beim Zusammenführen keine Konflikte auftreten.

Ich würde das folgende Modell empfehlen: 1. PK INT-Identität 2. Zusätzliche Spalte, die automatisch als UUID generiert wird.

Auf diese Weise ist der Zusammenführungsprozess möglich (UUID wäre Ihr REAL-Schlüssel, während die PK nur vorübergehend ist und Ihnen eine gute Leistung bietet).

HINWEIS: Die beste Lösung ist die Verwendung von NEWSEQUENTIALID (wie ich in den Kommentaren sagte), aber für ältere Apps, die nicht viel Zeit für die Umgestaltung haben (und noch schlimmer, nicht alle Einfügungen steuern), ist dies nicht möglich. Aber ab 2017 würde ich sagen, dass die beste Lösung hier NEWSEQUENTIALID ist oder Guid.Comb mit NHibernate macht.

Hoffe das hilft

Kat Lim Ruiz
quelle
Ich weiß nicht wirklich, was diese Begriffe bedeuten, aber Tatsache ist, dass die Indizes jeden Monat neu indiziert werden müssen. Wenn das, was Sie erwähnen, die Neuindizierungsaufgabe beseitigt, weiß ich es nicht, aber ich kann fragen.
Kat Lim Ruiz
3
Ich habe gedacht, dass dies für Eltern-Kind-Beziehungen möglicherweise nicht so gut funktioniert. In diesem Fall müssen Sie in der untergeordneten Tabelle Folgendes hinzufügen: parent-pk, parent-guid. Andernfalls können Referenzen zwischen Datenbanken verloren gehen. Ich habe nicht zu viel darüber nachgedacht oder ein Beispiel gemacht, aber dies könnte notwendig sein
Kat Lim Ruiz
4
@KatLimRuiz in SQL Server können Sie die NEWSEQUENTIALID () technet.microsoft.com/en-us/library/ms189786.aspx verwenden , um das Leistungsproblem zu vermeiden
Giammin
In der Tat, aber NEWSEQUENTIALID funktioniert nur als DEFAULT. Sie müssen also Ihr gesamtes DAL darum herum entwerfen, was für neue Projekte in Ordnung ist, aber für große Vermächtnisse nicht so einfach
Kat Lim Ruiz
@ KatLimRuiz Genie. Das ist ein großartiger Kompromiss
jmgunn87
26

Zu berücksichtigen ist, dass Autoincrements einzeln generiert werden und nicht mit einer parallelen Lösung gelöst werden können. Der Kampf um die Verwendung von UUIDs hängt letztendlich davon ab, was Sie erreichen möchten und was Sie möglicherweise opfern.

Zur Aufführung kurz :

Eine UUID wie die obige ist 36 Zeichen lang, einschließlich Bindestrichen. Wenn Sie dieses VARCHAR (36) speichern, wird die Vergleichsleistung drastisch verringert. Dies ist Ihr Primärschlüssel, Sie möchten nicht, dass er langsam ist.

Auf Bitebene beträgt eine UUID 128 Bit, was bedeutet, dass sie in 16 Bytes passt. Beachten Sie, dass dies nicht sehr gut lesbar ist, aber den Speicher niedrig hält und nur viermal größer ist als ein 32-Bit-Int oder 2 mal größer als ein 64-Bit-Int. Ich werde ein VARBINARY (16) verwenden. Theoretisch kann dies ohne viel Aufwand funktionieren.

Ich empfehle die folgenden zwei Beiträge zu lesen:

Ich rechne zwischen den beiden, sie beantworten Ihre Frage.

Kyle Rosendo
quelle
2
Eigentlich habe ich beide Artikel gelesen, bevor ich diese Frage gestellt habe, und ich hatte hier immer noch keine gute Antwort. Zum Beispiel sprechen Sie nicht über Typ 1 gegen Typ 4 UUIDS :(
Patrick Lightbody
Fair, ich habe meine Antwort ein wenig aktualisiert. Ich denke jedoch nicht, dass es zu viel zusätzlichen Einblick bietet.
Kyle Rosendo
@Patrick: Sie haben zu viele verschiedene Themen in Ihre Frage aufgenommen.
1
9 Jahre später, aber es sollte auch für die Nachwelt beachtet werden, dass Apps im Gegensatz zu ganzzahligen IDs UUIDs sicher generieren können, wodurch die Generierung vollständig aus der Datenbank entfernt wird. Die Manipulation der UUIDs zur Leistungsoptimierung (zeitstempelbasiert, aber so geändert, dass sie naiv sortiert werden können) ist in nahezu jeder anderen Sprache als SQL deutlich einfacher. Glücklicherweise verarbeiten heute fast alle Datenbanken (einschließlich MySQL) UUID-Primärschlüssel viel besser als früher.
Miles Elam
5

Ich neige dazu, UUID zu vermeiden, nur weil es ein Schmerz ist, sie zu speichern und als Primärschlüssel zu verwenden, aber es gibt Vorteile. Das wichtigste ist, dass sie EINZIGARTIG sind.

Normalerweise löse ich das Problem und vermeide UUID, indem ich Felder mit zwei Schlüsseln verwende.

COLLECTOR = EINZIGARTIG, DER EINER MASCHINE ZUGEWIESEN IST

ID = Vom Sammler gesammelter Datensatz (Feld auto_inc)

Das bietet mir zwei Dinge. Geschwindigkeit der Auto-Inc-Felder und Eindeutigkeit der Daten, die an einem zentralen Ort gespeichert werden, nachdem sie gesammelt und gruppiert wurden. Ich weiß auch beim Durchsuchen der Daten, wo sie gesammelt wurden, was für meine Bedürfnisse oft sehr wichtig ist.

Ich habe viele Fälle beim Umgang mit anderen Datensätzen für Kunden gesehen, in denen sie sich für die Verwendung von UUID entschieden haben, aber dann noch ein Feld haben, in dem die Daten gesammelt wurden, was wirklich eine Verschwendung von Aufwand ist. Es hilft wirklich, einfach zwei (oder mehr, falls erforderlich) Felder als Schlüssel zu verwenden.

Ich habe gerade zu viele Performance-Hits mit UUID gesehen. Sie fühlen sich wie ein Betrüger ...

Glenn J. Schworak
quelle
3

Wie wäre es, Schlüsselblöcke einzelnen Servern zuzuweisen, anstatt für jede Einfügung zentral eindeutige Schlüssel zu generieren? Wenn ihnen die Schlüssel ausgehen, können sie einen neuen Block anfordern. Dann lösen Sie das Overhead-Problem, indem Sie für jeden Einsatz eine Verbindung herstellen.

Der Keyserver verwaltet die nächste verfügbare ID

  • Server 1 fordert einen ID-Block an.
  • Keyserver gibt zurück (1.1000) Server 1 kann 1000 Datensätze einfügen, bis ein neuer Block angefordert werden
    muss
  • Server 2 fordert Indexblock an.
  • Keyserver kehrt zurück (1001.2000)
  • etc...

Sie könnten eine komplexere Version entwickeln, bei der ein Server die Anzahl der benötigten Schlüssel anfordern oder nicht verwendete Blöcke an den Schlüsselserver zurückgeben könnte, der dann natürlich eine Karte der verwendeten / nicht verwendeten Blöcke verwalten müsste.

Bouke Versteegh
quelle
Interessanter Vorschlag in der Theorie. Dies wäre in der Praxis komplex zu handhaben. Eine praktischere Lösung wäre wahrscheinlich die Antwort von schworak.
Simon East
2

Ich würde jedem Server auf transaktionale Weise eine numerische ID zuweisen. Dann erhöht jeder eingefügte Datensatz automatisch seinen eigenen Zähler. Die Kombination von ServerID und RecordID ist eindeutig. Das Feld "ServerID" kann indiziert werden, und die zukünftige Auswahlleistung basierend auf der ServerID (falls erforderlich) ist möglicherweise viel besser.

Nikolai
quelle
2

Die kurze Antwort lautet, dass viele Datenbanken aufgrund eines Konflikts zwischen ihrer Indizierungsmethode und der absichtlichen Entropie der UUIDs in den höherwertigen Bits Leistungsprobleme haben (insbesondere bei hohen INSERT-Volumes). Es gibt mehrere gängige Hacks:

  • Wählen Sie einen anderen Indextyp (z. B. unter MSSQL nicht gruppiert), der nichts dagegen hat
  • Munge die Daten, um die Entropie in Bits niedrigerer Ordnung zu verschieben (z. B. Neuordnung von Bytes von V1-UUIDs unter MySQL)
  • Machen Sie die UUID zu einem Sekundärschlüssel mit einem Auto-Inkrement-Int-Primärschlüssel

... aber das sind alles Hacks - und wahrscheinlich auch fragile.

Die beste, aber leider langsamste Antwort besteht darin, von Ihrem Anbieter zu verlangen, dass er sein Produkt verbessert, damit er wie jeder andere Typ UUIDs als Primärschlüssel verarbeiten kann. Sie sollten Sie nicht zwingen, Ihren eigenen halbgebackenen Hack zu würfeln, um das Versagen auszugleichen, das zu einem gängigen Anwendungsfall geworden ist und nur weiter wachsen wird.

StephenS
quelle
1

Was ist mit einer handgefertigten UID? Geben Sie jedem der Tausenden von Servern eine ID und machen Sie den Primärschlüssel zu einem Kombinationsschlüssel für die automatische Inkrementierung, MachineID ???

MindStalker
quelle
Ich habe darüber nachgedacht und muss möglicherweise einige Benchmarks durchführen. Sogar eine vorübergehende lokale Sequenz auf jeder der 1000 Maschinen in Kombination mit einem Zeitstempel könnte ausreichen. Beispiel: machine_id + temp_seq + timestamp
Patrick Lightbody
Ist es möglich, eine temp_sequence zu haben, die jeden Zeitstempel zurücksetzt? Ich bin mir nicht sicher.
MindStalker
1

Da der Primärschlüssel dezentral generiert wird, haben Sie ohnehin keine Möglichkeit, ein auto_increment zu verwenden.

Wenn Sie die Identität der Remotecomputer nicht verbergen müssen, verwenden Sie UUIDs vom Typ 1 anstelle von UUIDs. Sie sind einfacher zu generieren und können die Leistung der Datenbank zumindest nicht beeinträchtigen.

Das gleiche gilt für varchar (char, wirklich) vs. binär: Es kann nur helfen. Ist es wirklich wichtig, wie viel Leistung verbessert wird?


quelle