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.
quelle
Antworten:
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
quelle
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 haben
on 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.
quelle
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
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.
quelle
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:
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
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,
quelle