it-swarm.com.de

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

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

Ich habe eine Issues-Tabelle und darin befinden sich 12 Fremdschlüssel, die mit verschiedenen anderen Tabellen verknüpft sind. Von diesen 8 müssten ich beitreten, um das Titelfeld aus den anderen Tabellen zu erhalten, damit der Datensatz in einer Webanwendung Sinn ergibt, aber dann bedeutet dies, 8 Verknüpfungen durchzuführen, was wirklich übertrieben erscheint, zumal ich nur einziehe 1 Feld für jede dieser Verknüpfungen.

Jetzt wurde mir auch gesagt, dass ich aus Gründen der Dauerhaftigkeit einen automatisch inkrementierenden Primärschlüssel verwenden soll (es sei denn, Sharding ist ein Problem. In diesem Fall sollte ich eine GUID verwenden). Aber wie schlecht ist es, einen Varchar (maximale Länge 32) in Bezug auf die Leistung zu verwenden? Ich meine, die meisten dieser Tabellen werden wahrscheinlich nicht viele Datensätze haben (die meisten sollten unter 20 sein). Auch wenn ich den Titel als Primärschlüssel verwende, muss ich in 95% der Fälle keine Joins durchführen. Bei 95% der SQL würde ich sogar einen Leistungseinbruch erleiden (glaube ich). Der einzige Nachteil, den ich mir vorstellen kann, ist, dass ich eine höhere Speicherplatznutzung habe (aber ein Tag weniger ist das wirklich eine große Sache).

Der Grund, warum ich für viele dieser Dinge Nachschlagetabellen 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 Varchars als Primärschlüssel für eine Tabelle, die nicht viele Datensätze enthält?

PDATE - Einige Tests

Also habe ich beschlossen, einige grundlegende Tests für dieses 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.
  • Bestellen nach (it.title für int FK, i.issueTypeId für varchar FK)
  • Limit (50000, 100)
  • Gruppieren und begrenzen Sie zusammen
  • Gruppieren, ordnen und begrenzen Sie zusammen

Die Ergebnisse für diese waren:

Abfragetyp: VARCHAR FK TIME/INT FK TIME


Basisabfrage: ~ 4 ms/~ 52 ms

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

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

Bestellung von: ~ 231ms/~ 2sec

Limit: ~ 67 ms/~ 343 ms

Gruppieren und begrenzen Sie zusammen: ~ 504 ms/~ 2 s

Gruppieren, ordnen und begrenzen Sie zusammen: ~ 504 ms/~ 2,3 s

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

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

13
ryanzec

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 entscheide ich mich für 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 Verknüpfungen ausgeführt werden - die Verknüpfung mit Varchars gegenüber Ganzzahlen ist mit zunehmender Länge des Primärschlüssels etwa 2x bis 3x langsamer, sodass Sie Ihre Schlüssel als Ganzzahlen haben möchten. 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, möglicherweise ein CHAR (2) für den zweistelligen Code, aber ich würde mich trotzdem für einen winzigen Punkt entscheiden (128) für die Spalte gegen einen großen Int, der bis zu 2 Milliarden betragen kann

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

Entscheiden Sie sich für sequentielle automatische Inkrementierung von Ganzzahlen für Ihre Primärschlüssel und erzielen Sie die integrierte Effizienz, die Datenbanksysteme mit Unterstützung für zukünftige Änderungen bieten

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 Verbinden vieler Tabellen.
Ein Nachteil des Varchar-Primärschlüssels ist die Erhöhung der Indexgröße, wie atxdba hervorhebt. 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 vielen kaskadierenden Aktualisierungen führt. Nicht alle RDMS, zum Beispiel Oracle, lassen Sie sogar on update cascade. Im Allgemeinen wird das Ändern des Primärschlüsselwerts als sehr schlechte Praxis angesehen. Ich möchte nicht sagen, dass natürliche Primärschlüssel immer böse sind. Wenn die Suchwerte klein sind und sich nie ändern, kann dies akzeptabel sein.

Eine Option, die Sie möglicherweise 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 anzeigen müssen. Wenn die Leistung akzeptabel ist, kämpfen Sie nicht mit dem Problem, das derzeit nicht besteht.

6
a1ex07

Der größte Nachteil ist die Wiederholung der PK. Sie haben auf eine Zunahme der Speicherplatznutzung hingewiesen, aber um klar zu sein, ist die erhöhte 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 genug haben RAM, 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 anderen MySQL-Overhead und -Decache belassen. Und das setzt voraus, dass es sich um einen dedizierten DB-Server handelt. Wenn auf dem System andere Dinge ausgeführt werden, müssen Sie auch deren Anforderungen berücksichtigen.

3
atxdba

Zusätzlich zur @ atxdba-Antwort, die Ihnen erklärte, warum die Verwendung von Zahlen für den Speicherplatz besser ist, wollte 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 bereits erwähnt, Nachschlagetabellen sind, sodass INT FK TINYINT FK sein kann für 20 Felder ein 20-Byte-Datensatz. Ich weiß, dass sich für mehrere Hundert Datensätze nicht viel ändern wird, aber wenn Sie mehrere Millionen erreichen, werden Sie es wahrscheinlich zu schätzen wissen, Platz zu sparen

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

Hoffe es könnte helfen,

1
Spredzy