Mysql int vs varchar als Primärschlüssel (InnoDB Storage Engine?


13

Ich erstelle eine Webanwendung (Projektmanagementsystem) und habe mich darüber gewundert, was die Leistung angeht.

Ich habe eine Issues-Tabelle und darin befinden sich 12 Fremdschlüssel, die mit verschiedenen anderen Tabellen verknüpft sind. 8 davon müsste ich verbinden, um das Titelfeld von den anderen Tabellen abzurufen, damit der Datensatz in einer Webanwendung einen Sinn ergibt. Dann bedeutet es jedoch, 8 Verknüpfungen auszuführen, was wirklich übertrieben erscheint, insbesondere, weil ich nur einbezogen bin 1 Feld für jeden dieser Joins.

Jetzt wurde mir auch gesagt, dass ich einen automatisch inkrementierenden Primärschlüssel verwenden soll (es sei denn, das Splittern ist ein Problem, in welchem ​​Fall ich eine GUID verwenden sollte). Aber wie schlecht ist es, einen varchar (max. Länge 32) in Bezug auf die Leistung zu verwenden? Ich meine, die meisten dieser Tabellen werden wahrscheinlich nicht viele Datensätze enthalten (die meisten sollten unter 20 sein). Auch wenn ich den Titel als Primärschlüssel verwende, muss ich 95% der Zeit keine Joins ausführen, sodass bei 95% der SQL sogar Leistungseinbußen auftreten (glaube ich). Der einzige Nachteil, den ich mir vorstellen kann, ist, dass ich mehr Speicherplatz verbrauchen werde (aber ein Tag weniger ist das wirklich eine große Sache).

Der Grund, warum ich Nachschlagetabellen für viele dieser Dinge anstelle von Aufzählungen verwende, ist, dass alle diese Werte vom Endbenutzer über die Anwendung selbst konfiguriert werden müssen.

Was sind die Nachteile der Verwendung eines varchar als Primärschlüssel für eine Tabelle, die nicht über viele Datensätze verfügt?

UPDATE - Einige Tests

Also habe ich mich entschlossen, ein paar grundlegende Tests mit diesem Zeug durchzuführen. Ich habe 100000 Datensätze und dies sind die Basisabfragen:

Basis-VARCHAR-FK-Abfrage

SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle, 
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle, 
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate, 
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp, 
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i

Basis INT FK-Abfrage

SELECT i.id, i.key, i.title, ru.username as reporterUserUsername, 
au.username as assignedUserUsername, p.title as projectTitle, 
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle, 
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle, 
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId, 
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp, 
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId

Ich habe diese Abfrage auch mit den folgenden Ergänzungen ausgeführt:

  • Wählen Sie ein bestimmtes Element aus (wobei i.key = 43298).
  • Gruppieren nach i.id.
  • Order by (it.title für int FK, i.issueTypeId für varchar FK)
  • Limit (50000, 100)
  • Zusammen gruppieren und begrenzen
  • Gruppieren, ordnen und begrenzen Sie gemeinsam

Die Ergebnisse für diese wo:

Abfragetyp: VARCHAR FK TIME / INT FK TIME


Basisabfrage: ~ 4ms / ~ 52ms

Wählen Sie ein bestimmtes Element aus: ~ 140ms / ~ 250ms

Gruppieren nach i.id .: ~ 4 ms / ~ 2,8 s

Ordnen nach: ~ 231ms / ~ 2sec

Limit: ~ 67 ms / ~ 343 ms

Zusammen gruppieren und begrenzen: ~ 504ms / ~ 2sec

Zusammen gruppieren, bestellen und begrenzen: ~ 504ms / ~ 2,3sec

Jetzt weiß ich nicht, welche Konfiguration ich vornehmen könnte, um die eine oder die andere (oder beide) schneller zu machen, aber es scheint, als würde der VARCHAR FK in Abfragen nach Daten schneller sehen (manchmal viel schneller).

Ich denke, ich muss mich entscheiden, ob diese Geschwindigkeitsverbesserung die zusätzliche Daten- / Indexgröße wert ist.


Ihre Tests weisen auf etwas hin. Ich würde auch mit verschiedenen InnoDB-Einstellungen (Pufferpools usw.) testen, da die Standard-MySQL-Einstellungen nicht wirklich für InnoDB optimiert sind.
ypercubeᵀᴹ

Sie sollten auch die Leistung beim Einfügen / Aktualisieren / Löschen testen, da dies auch durch die Indexgröße beeinflusst werden kann. Der einzige Cluster-Schlüssel jeder InnoDB-Tabelle ist normalerweise der PK, und diese (PK) -Spalte ist auch in jedem anderen Index enthalten. Dies ist wahrscheinlich ein großer Nachteil von großen PKs in InnoDB und vielen Indizes auf dem Tisch (aber 32 Bytes sind eher mittelgroß, nicht groß, es kann also kein Problem sein).
ypercubeᵀᴹ

Sie sollten auch mit größeren Tabellen (im Bereich von beispielsweise 10-100 Millionen Zeilen oder größer) testen, wenn Sie erwarten, dass Ihre Tabellen über 100 KB (was nicht wirklich groß ist) anwachsen.
ypercubeᵀᴹ

@ypercube Also erhöhe ich die Daten auf 2 Millionen und die select-Anweisung für den int-FK wird exponentiell langsamer, wobei der varchar-Fremdschlüssel ziemlich stabil bleibt. Ein Gedanke, dass der varchar den Preis in Festplatten- / Speicheranforderungen für den Gewinn in ausgewählten Abfragen wert ist (was für diese bestimmte Tabelle und einige andere kritisch sein wird).
Ryanzec

Überprüfen Sie auch Ihre Einstellungen für die Datenbank (und insbesondere für InnoDB), bevor Sie zu Schlussfolgerungen gelangen. Bei kleinen Referenztabellen würde ich keinen exponentiellen Anstieg erwarten
ypercubeᵀᴹ

Antworten:


9

Ich befolge die folgenden Regeln für Primärschlüssel:

a) Sollte keine geschäftliche Bedeutung haben - sie sollten völlig unabhängig von der Anwendung sein, die Sie entwickeln, daher setze ich auf numerische, automatisch generierte Ganzzahlen. Wenn Sie jedoch zusätzliche Spalten benötigen, um eindeutig zu sein, erstellen Sie eindeutige Indizes, um dies zu unterstützen

b) Sollte in Joins ausgeführt werden - Das Verbinden von Varchars mit Ganzzahlen ist mit zunehmender Länge des Primärschlüssels etwa 2x bis 3x langsamer. Daher möchten Sie, dass Ihre Schlüssel als Ganzzahlen vorliegen. Da alle Computersysteme binär sind, vermute ich, dass die Zeichenfolge in binär geändert und dann mit den anderen verglichen wird, was sehr langsam ist

c) Verwenden Sie den kleinstmöglichen Datentyp. Wenn Sie erwarten, dass Ihre Tabelle nur sehr wenige Spalten enthält, z. B. 52 US-Bundesstaaten, verwenden Sie den kleinstmöglichen Typ, z. B. CHAR (2) für den zweistelligen Code (128) für die Spalte gegen ein großes int, das bis zu 2billion gehen kann

Außerdem haben Sie eine Herausforderung, wenn Sie Ihre Änderungen von den Primärschlüsseln auf die anderen Tabellen kaskadieren müssen, wenn sich beispielsweise der Projektname ändert (was nicht ungewöhnlich ist).

Entscheiden Sie sich für sequentielle, automatisch inkrementierende Ganzzahlen für Ihre Primärschlüssel und profitieren Sie von den integrierten Effizienzvorteilen, die Datenbanksysteme für zukünftige Änderungen bieten


1
Zeichenfolgen werden nicht in binär geändert. Sie sind von Anfang an binär gespeichert. Wie würden sie sonst aufbewahrt werden? Vielleicht denken Sie über Operationen nach, um einen Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung zu ermöglichen?
Jon of All Trades

6

In Ihren Tests vergleichen Sie nicht den Leistungsunterschied zwischen varchar und int keys, sondern die Kosten für mehrere Joins. Es ist nicht überraschend, dass das Abfragen einer Tabelle schneller ist als das Verknüpfen vieler Tabellen.
Ein Nachteil des varchar-Primärschlüssels ist das Erhöhen der Indexgröße, wie atxdba hervorhob . Selbst wenn Ihre Nachschlagetabelle keine anderen Indizes außer PK enthält (was ziemlich unwahrscheinlich, aber möglich ist), hat jede Tabelle, die auf die Nachschlagetabelle verweist, einen Index für diese Spalte.
Eine weitere schlechte Sache bei natürlichen Primärschlüsseln ist, dass sich ihr Wert ändern kann, was zu zahlreichen kaskadierenden Aktualisierungen führt. Nicht alle RDMS, zum Beispiel Oracle, lassen Sie auch habenon update cascade. Im Allgemeinen wird das Ändern des Primärschlüsselwerts als sehr schlechte Vorgehensweise angesehen. Ich möchte nicht sagen, dass natürliche Primärschlüssel immer böse sind. Wenn Nachschlagewerte klein sind und sich nie ändern, denke ich, dass dies akzeptabel sein kann.

Eine Option, die Sie in Betracht ziehen möchten, ist die Implementierung einer materialisierten Ansicht. Mysql unterstützt es nicht direkt, aber Sie können die gewünschte Funktionalität mit Triggern für zugrunde liegende Tabellen erreichen. Sie haben also eine Tabelle, die alles enthält, was Sie zum Anzeigen benötigen. Wenn die Leistung akzeptabel ist, sollten Sie sich nicht mit dem derzeit nicht vorhandenen Problem herumschlagen.


3

Der größte Nachteil ist die Wiederholung der PK. Sie haben auf eine Zunahme des Speicherplatzbedarfs hingewiesen, aber um genau zu sein, ist die Zunahme der Indexgröße Ihr größeres Anliegen. Da innodb ein Clustered-Index ist, speichert jeder Sekundärindex intern eine Kopie der PK, mit der letztendlich übereinstimmende Datensätze gefunden werden.

Sie sagen, es wird erwartet, dass Tabellen "klein" sind (20 Zeilen sind in der Tat sehr klein). Wenn Sie über genügend RAM verfügen, um innodb_buffer_pool_size gleich zu setzen

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

Dann mach das und du wirst wahrscheinlich hübsch sitzen. In der Regel möchten Sie jedoch mindestens 30% - 40% des gesamten Systemspeichers für andere Mysql-Overhead- und -Discache-Vorgänge übrig lassen. Und das setzt voraus, dass es sich um einen dedizierten DB-Server handelt. Wenn andere Dinge auf dem System ausgeführt werden, müssen Sie auch deren Anforderungen berücksichtigen.


1

Zusätzlich zu @atxdba answer - was Ihnen erklärt, warum die Verwendung von Zahlen für den Speicherplatz besser ist, möchte ich zwei Punkte hinzufügen:

  1. Wenn Ihre Issues-Tabelle auf VARCHAR FK basiert und Sie beispielsweise 20 kleine VARCHAR (32) FK haben, kann Ihr Datensatz eine Länge von 20 x 32 Byte erreichen, während die anderen Tabellen, wie Sie bereits erwähnt haben, Nachschlagetabellen sind für 20 Felder werden 20 Bytes gespeichert. Ich weiß, dass sich für einige Hundert Datensätze nicht viel ändert, aber wenn Sie mehrere Millionen erreichen, werden Sie es zu schätzen wissen, Platz zu sparen

  2. Für das Geschwindigkeitsproblem würde ich die Verwendung von Abdeckungsindizes in Betracht ziehen, da für diese Abfrage anscheinend nicht so viele Daten aus Nachschlagetabellen abgerufen werden, dass ich den mit VARCHAR FK / W / COVERING bereitgestellten Test erneut durchführen würde INDEX UND reguläres INT FK.

Hoffe, es könnte helfen,

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.