it-swarm.com.de

Gibt es einen echten Leistungsunterschied zwischen den Primärschlüsseln INT und VARCHAR?

Gibt es einen messbaren Leistungsunterschied zwischen der Verwendung von INT und VARCHAR als Primärschlüssel in MySQL? Ich möchte VARCHAR als Primärschlüssel für Referenzlisten verwenden (z. B. US-Bundesstaaten, Ländercodes), und ein Kollege wird den INT AUTO_INCREMENT nicht als Primärschlüssel für alle Tabellen verwenden. 

Mein Argument, wie ausführlich hier , ist, dass der Leistungsunterschied zwischen INT und VARCHAR vernachlässigbar ist, da für jede INT-Fremdschlüsselreferenz ein JOIN erforderlich ist, um die Referenz zu verstehen. Ein VARCHAR-Schlüssel gibt die Informationen direkt wieder.

Hat also jemand Erfahrung mit diesem speziellen Anwendungsfall und den damit verbundenen Leistungsproblemen?

147
Jake McGraw

Es ist ein guter Punkt, dass Sie einige zusammengefügte Abfragen vermeiden können, indem Sie einen so genannten natürlichen Schlüssel anstelle eines Ersatzzeichens verwenden. Nur Sie können beurteilen, ob der Nutzen Ihrer Bewerbung von erheblichem Nutzen ist. 

Das heißt, Sie können die Abfragen in Ihrer Anwendung messen, die für die Schnelligkeit am wichtigsten sind, da sie mit großen Datenmengen arbeiten oder sehr häufig ausgeführt werden. Wenn diese Abfragen von der Beseitigung eines Joins profitieren und nicht durch die Verwendung eines varchar-Primärschlüssels beeinträchtigt werden, tun Sie dies.

Verwenden Sie keine Strategie für alle Tabellen in Ihrer Datenbank. In einigen Fällen ist es wahrscheinlich, dass ein natürlicher Schlüssel besser ist, in anderen Fällen ist ein Ersatzschlüssel jedoch besser. 

Andere Leute behaupten, dass es in der Praxis selten ist, dass ein natürlicher Schlüssel niemals geändert wird oder Duplikate hat, sodass Ersatzschlüssel in der Regel lohnenswert sind.

69
Bill Karwin

Es geht nicht um Leistung. Es geht darum, was einen guten Primärschlüssel ausmacht. Einzigartig und unveränderlich im Laufe der Zeit. Sie denken vielleicht, dass sich eine Entität wie ein Ländercode im Laufe der Zeit nicht ändert und ein guter Kandidat für einen Primärschlüssel wäre. Aber bittere Erfahrung ist das selten.

INT AUTO_INCREMENT erfüllt die Bedingung "einzigartig und unveränderlich". Daher die Präferenz.

73
Steve McLeod

Abhängig von der Länge. Wenn der Varchar 20 Zeichen hat und der Int 4 ist, dann hat Ihr Index FÜNF mal so viele Knoten pro Seite des Indexspeicherplatzes auf der Festplatte ... Das bedeutet, dass der Durchlauf erfolgt Der Index erfordert ein Fünftel so viele physische und/oder logische Lesevorgänge. 

Wenn Leistung ein Problem ist, verwenden Sie bei gegebener Gelegenheit immer einen integrierten, nicht sinnvollen Schlüssel (als Ersatz bezeichnet) für Ihre Tabellen und für Fremdschlüssel, die auf die Zeilen in diesen Tabellen verweisen. 

Zur gleichen Zeit, um die Konsistenz der Daten zu gewährleisten, sollte jede Tabelle, in der es darauf ankommt,aucheinen sinnvollen nicht-numerischen alternativen Schlüssel (oder einen eindeutigen Index) haben, um sicherzustellen, dass keine doppelten Zeilen vorhanden sind eingefügt (Duplikat basierend auf aussagekräftigen Tabellenattributen). 

Für die spezifische Verwendung, über die Sie sprechen (wie Status-Lookups), spielt es keine Rolle, da die Größe der Tabelle so klein ist. Im Allgemeinen haben Indizes für Tabellen mit weniger als ein paar tausend Zeilen keinen Einfluss auf die Leistung. .. 

33
Charles Bretana

Absolut nicht.

Ich habe mehrere ... mehrere ... Leistungsprüfungen zwischen INT, VARCHAR und CHAR durchgeführt.

Eine 10 Millionen-Rekordtabelle mit einem PRIMARY KEY (einzigartig und gruppiert) hatte die gleiche Geschwindigkeit und Leistung (und Teilbaumkosten), unabhängig davon, welche der drei ich verwendet hatte.

Davon abgesehen ... verwenden Sie das Beste für Ihre Anwendung. Mach dir keine Sorgen über die Leistung.

30
Timothy Khouri

Ich war ein bisschen verärgert über das Fehlen von Benchmarks für dieses Online, also habe ich selbst einen Test durchgeführt.

Beachten Sie jedoch, dass ich es nicht regelmäßig mache. Überprüfen Sie daher mein Setup und meine Schritte auf Faktoren, die das Ergebnis unbeabsichtigt beeinflusst haben könnten, und schreiben Sie Ihre Bedenken in die Kommentare.

Das Setup war wie folgt:

  • Intel® Core ™ i7-7500U-CPU bei 2,70 GHz × 4
  • 15.6 GiB RAM, von dem ich sicherstellte, dass ungefähr 8 GB während des Tests frei waren.
  • 148,6 GB SSD-Laufwerk mit viel freiem Speicherplatz.
  • Ubuntu 16.04 64-Bit
  • MySQL Ver 14.14 Distrib 5.7.20 für Linux (x86_64)

Die Tische:

create table jan_int (data1 varchar(255), data2 int(10), myindex tinyint(4)) ENGINE=InnoDB;
create table jan_int_index (data1 varchar(255), data2 int(10), myindex tinyint(4), INDEX (myindex)) ENGINE=InnoDB;
create table jan_char (data1 varchar(255), data2 int(10), myindex char(6)) ENGINE=InnoDB;
create table jan_char_index (data1 varchar(255), data2 int(10), myindex char(6), INDEX (myindex)) ENGINE=InnoDB;
create table jan_varchar (data1 varchar(255), data2 int(10), myindex varchar(63)) ENGINE=InnoDB;
create table jan_varchar_index (data1 varchar(255), data2 int(10), myindex varchar(63), INDEX (myindex)) ENGINE=InnoDB;

Dann füllte ich 10 Millionen Zeilen in jeder Tabelle mit einem PHP Skript, dessen Wesen so ist:

$pdo = get_pdo();

$keys = [ 'alabam', 'massac', 'newyor', 'newham', 'delawa', 'califo', 'nevada', 'texas_', 'florid', 'ohio__' ];

for ($k = 0; $k < 10; $k++) {
    for ($j = 0; $j < 1000; $j++) {
        $val = '';
        for ($i = 0; $i < 1000; $i++) {
            $val .= '("' . generate_random_string() . '", ' . Rand (0, 10000) . ', "' . ($keys[Rand(0, 9)]) . '"),';
        }
        $val = rtrim($val, ',');
        $pdo->query('INSERT INTO jan_char VALUES ' . $val);
    }
    echo "\n" . ($k + 1) . ' millon(s) rows inserted.';
}

Für int -Tabellen wurde das Bit ($keys[Rand(0, 9)]) Durch nur Rand(0, 9) ersetzt, und für varchar -Tabellen habe ich vollständige US-Bundesstaatsnamen verwendet, ohne sie auszuschneiden oder zu erweitern sie auf 6 Zeichen. generate_random_string() generiert eine 10-stellige Zufallszeichenfolge.

Dann lief ich in MySQL:

  • SET SESSION query_cache_type=0;
  • Für Tabelle jan_int:
    • SELECT count(*) FROM jan_int WHERE myindex = 5;
    • SELECT BENCHMARK(1000000000, (SELECT count(*) FROM jan_int WHERE myindex = 5));
  • Für andere Tabellen gilt dasselbe wie oben, mit myindex = 'califo' Für char Tabellen und myindex = 'california' Für varchar Tabellen.

Zeiten der Abfrage BENCHMARK für jede Tabelle:

  • jan_int: 21.30 sek
  • jan_int_index: 18,79 sek
  • jan_char: 21,70 sek
  • jan_char_index: 18,85 sek
  • jan_varchar: 21,76 Sek
  • jan_varchar_index: 18,86 sek

In Bezug auf Tabellen- und Indexgrößen ist hier die Ausgabe von show table status from janperformancetest; (Mit einigen nicht gezeigten Spalten):

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Name              | Engine | Version | Row_format | Rows    | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Collation              |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| jan_int           | InnoDB |      10 | Dynamic    | 9739094 |             43 |   422510592 |               0 |            0 |   4194304 |           NULL | utf8mb4_unicode_520_ci |  
| jan_int_index     | InnoDB |      10 | Dynamic    | 9740329 |             43 |   420413440 |               0 |    132857856 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_char          | InnoDB |      10 | Dynamic    | 9726613 |             51 |   500170752 |               0 |            0 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_char_index    | InnoDB |      10 | Dynamic    | 9719059 |             52 |   513802240 |               0 |    202342400 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_varchar       | InnoDB |      10 | Dynamic    | 9722049 |             53 |   521142272 |               0 |            0 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_varchar_index | InnoDB |      10 | Dynamic    | 9738381 |             49 |   486539264 |               0 |    202375168 |   7340032 |           NULL | utf8mb4_unicode_520_ci | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Mein Fazit ist, dass es für diesen speziellen Anwendungsfall keinen Leistungsunterschied gibt.

28
Jan Żankowski

Bei kurzen Codes gibt es wahrscheinlich keinen Unterschied. Dies gilt insbesondere, da die Tabelle, die diese Codes enthält, wahrscheinlich sehr klein ist (höchstens ein paar tausend Zeilen) und nicht häufig geändert wird (wann haben wir zuletzt einen neuen US-Bundesstaat hinzugefügt).

Bei größeren Tischen mit einer größeren Variation zwischen den Schlüsseln kann dies gefährlich sein. Denken Sie beispielsweise an die Verwendung der E-Mail-Adresse/des Benutzernamens aus einer Benutzertabelle. Was passiert, wenn Sie einige Millionen Benutzer haben und einige dieser Benutzer lange Namen oder E-Mail-Adressen haben? Jedes Mal, wenn Sie diese Tabelle mithilfe dieses Schlüssels verknüpfen müssen, wird sie erheblich teurer.

9
Joel Coehoorn

Was den Primärschlüssel betrifft, sollte als Primärschlüssel festgelegt werden, was physisch eine Zeile eindeutig macht. 

Für eine Referenz als Fremdschlüssel ist die Verwendung einer automatisch inkrementierenden Ganzzahl als Ersatz eine Nizza-Idee aus zwei Hauptgründen.
- Erstens ist in der Regel weniger Aufwand für den Join entstanden.
- Zweitens: Wenn Sie die Tabelle mit dem eindeutigen varchar aktualisieren müssen, muss das Update auf alle untergeordneten Tabellen heruntergefahren und alle sowie die Indizes aktualisiert werden, wohingegen es beim int-Ersatz nur die Aktualisierung der Tabellen erfordert Haupttabelle und ihre Indizes.

Der Vorteil der Verwendung des Ersatzzeichens ist, dass Sie möglicherweise eine Änderung der Bedeutung des Ersatzzeichens zulassen:

ex.
id value
1 A
2 B
3 C

Update 3 to D
id value
1 A
2 B
3 D

Update 2 to C
id value
1 A
2 C
3 D

Update 3 to B
id value
1 A
2 C
3 B

Es hängt alles davon ab, worüber Sie sich in Ihrer Struktur wirklich Sorgen machen müssen und was am meisten bedeutet.

6
LeppyR64

Häufige Fälle, in denen ein Ersatzcode AUTO_INCREMENT wehtut:

Ein allgemeines Schemamuster ist eine Many-to-Many-Zuordnung :

CREATE TABLE map (
    id ... AUTO_INCREMENT,
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(id),
    UNIQUE(foo_id, bar_id),
    INDEX(bar_id) );

Die Leistung dieses Musters ist viel besser, insbesondere bei der Verwendung von InnoDB:

CREATE TABLE map (
    # No surrogate
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(foo_id, bar_id),
    INDEX      (bar_id, foo_id) );

Warum?

  • InnoDB-Sekundärschlüssel benötigen eine zusätzliche Suche. Durch das Verschieben des Paares in die PK wird dies für eine Richtung vermieden.
  • Der Sekundärindex ist "abdeckend", sodass keine zusätzliche Suche erforderlich ist.
  • Diese Tabelle ist kleiner, da id und ein Index entfernt werden.

Ein anderer Fall ( Land ):

country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii

Allzu oft normalisiert der Neuling country_code in eine 4-Byte-Variable INT, anstatt einen "natürlichen" 2-Byte-String zu verwenden, der fast unverändert ist. Schneller, kleiner, weniger JOINs, lesbarer.

2
Rick James

Die Frage bezieht sich auf MySQL, also sage ich, dass es einen signifikanten Unterschied gibt. Wenn es um Oracle ging (das Zahlen als String speichert - ja, ich konnte es zuerst nicht glauben) -, dann nicht viel Unterschied.

Das Speichern in der Tabelle ist nicht das Problem, sondern das Aktualisieren und Verweisen auf den Index ist. Abfragen, bei denen ein Datensatz anhand seines Primärschlüssels gesucht wird, sind häufig - Sie möchten, dass sie so schnell wie möglich ausgeführt werden, da sie so häufig vorkommen.

Die Sache ist, dass eine CPU natürlich mit 4 Byte und 8 Byte Ganzzahlen handelt, in silicon . Es ist wirklich schnell für den Vergleich von zwei ganzen Zahlen - es geschieht in einem oder zwei Taktzyklen. 

Sehen Sie sich nun eine Zeichenfolge an - sie besteht aus vielen Zeichen (heutzutage mehr als ein Byte pro Zeichen). Der Vergleich von zwei Zeichenfolgen für die Rangfolge kann nicht in einem oder zwei Zyklen durchgeführt werden. Stattdessen müssen die Zeichen der Zeichenfolge wiederholt werden, bis ein Unterschied gefunden wird. Ich bin sicher, es gibt Tricks, um es in einigen Datenbanken schneller zu machen, aber das ist hier irrelevant, da ein Int-Vergleich von der CPU natürlich und blitzschnell in Silizium ausgeführt wird.

Meine allgemeine Regel - jeder Primärschlüssel sollte ein autoincrementierender INT sein, insbesondere in OO - Anwendungen, die einen ORM (Hibernate, Datanucleus, was auch immer) verwenden, wo es viele Beziehungen zwischen Objekten gibt - sie werden normalerweise immer als einfaches FK und implementiert Die Fähigkeit der DB, diese schnell aufzulösen, ist wichtig für die Reaktionsfähigkeit Ihrer Anwendung.

2
Volksman

Bei HauteLook haben wir viele unserer Tabellen geändert, um natürliche Schlüssel zu verwenden. Wir haben eine echte Leistungssteigerung erlebt. Wie Sie bereits erwähnt haben, verwenden viele unserer Abfragen jetzt weniger Joins, wodurch die Abfragen performanter werden. Wir werden sogar einen zusammengesetzten Primärschlüssel verwenden, wenn dies sinnvoll ist. Allerdings ist es mit einigen Tabellen einfacher, mit einem Ersatzschlüssel zu arbeiten.

Wenn Sie den Benutzern das Schreiben von Schnittstellen in Ihre Datenbank ermöglichen, kann ein Ersatzschlüssel hilfreich sein. Der Dritte kann sich darauf verlassen, dass sich der Ersatzschlüssel nur in sehr seltenen Fällen ändert.

Ich stand vor demselben Dilemma. Ich habe eine DW (Constellation Schema) mit 3 Faktentabellen erstellt, Straßenunfälle, Unfallfahrzeuge und Unfallfälle. Die Daten umfassen alle in Großbritannien von 1979 bis 2012 aufgezeichneten Unfälle und 60 Maßtabellen. Insgesamt etwa 20 Millionen Datensätze.

Faktentabellenbeziehungen:

+----------+          +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1      * +----v----+
     1|                    |1
      |    +----------+    |
      +---<| Casualty |>---+
         * +----------+ *

RDMS: MySQL 5.6

Der Unfallindex ist ein Varchar (Zahlen und Buchstaben) mit 15 Ziffern. Ich habe versucht, keine Ersatzschlüssel zu haben, sobald sich die Unfallindizes nie ändern würden ..__ In einem i7-Computer (8 Kerne) wurde die DW zu langsam, um nach 12 Millionen Datensätzen der Last abhängig von den Abmessungen abzurufen Nacharbeiten und Hinzufügen von Bigint-Surrogat-Schlüsseln habe ich im Durchschnitt um 20% gesteigert. Allerdings noch zu wenig Leistungszuwachs, aber gültiger Versuch. Ich arbeite in MySQL Tuning und Clustering.

1
Diego Duarte

Gestatten Sie mir zu sagen, dass es definitiv einen Unterschied gibt, wenn Sie den Umfang der Leistung berücksichtigen (Out of the Box-Definition):

1- Die Verwendung von surrogate int ist in der Anwendung schneller, da Sie ToUpper (), ToLower (), ToUpperInvarient () oder ToLowerInvarient () in Ihrem Code oder in Ihrer Abfrage nicht verwenden müssen. Siehe hierzu die Microsoft-Leistungsregeln. (Ausführung der Anwendung)

2- Die Verwendung von Surrogate Int garantiert, dass der Schlüssel im Laufe der Zeit nicht geändert wird. Selbst Ländercodes können sich ändern, siehe Wikipedia, wie sich ISO-Codes im Laufe der Zeit geändert haben. Dies würde viel Zeit in Anspruch nehmen, um den Primärschlüssel für Teilbäume zu ändern. (Leistung der Datenpflege)

3- Anscheinend gibt es Probleme mit ORM-Lösungen, z. B. NHibernate, wenn PK/FK nicht int ist. (Entwicklerleistung)

0
Shadi Namrouti

Nicht sicher über die Auswirkungen auf die Leistung, aber es scheint ein möglicher Kompromiss zu sein, zumindest während der Entwicklung, den automatisch inkrementierten Ganzzahl-Surrogat-Schlüssel sowie Ihren beabsichtigten, eindeutigen "natürlichen" Schlüssel einzuschließen. Dies gibt Ihnen die Möglichkeit, die Leistung sowie andere mögliche Probleme, einschließlich der Veränderbarkeit natürlicher Schlüssel, zu bewerten.

0
George Jempty

Wie üblich gibt es keine pauschalen Antworten. 'Es hängt davon ab, ob!' und ich bin nicht verrückt. Ich verstehe die ursprüngliche Frage für Schlüssel in kleinen Tabellen - beispielsweise als Land (Integer-ID oder Char/Varchar-Code), der ein Fremdschlüssel für eine potenziell große Tabelle ist, beispielsweise eine Adress-/Kontakttabelle.

Es gibt zwei Szenarien, wenn Sie Daten aus der DB zurückholen möchten. Erstens ist eine Abfrage mit Listen-/Suchart, in der Sie alle Kontakte mit Bundes- und Landeskennzahlen oder Namen auflisten möchten (IDs helfen nicht und benötigen daher eine Suche). Das andere ist ein Get-Szenario für den Primärschlüssel, das einen einzelnen Kontaktdatensatz anzeigt, bei dem der Name des Staates, des Landes angezeigt werden muss.

Für letzteres ist es wahrscheinlich egal, worauf der FK basiert, da wir Tabellen für einen einzelnen Datensatz oder einige Datensätze und Schlüssellesen zusammenstellen. Das vorherige Szenario (Suche oder Liste) kann von unserer Wahl beeinflusst werden. Da es erforderlich ist, das Land anzuzeigen (zumindest einen erkennbaren Code und vielleicht sogar die Suche selbst enthält einen Ländercode), kann es nicht möglich sein, eine andere Tabelle über einen Ersatzschlüssel anzuschließen (ich bin hier nur vorsichtig, weil ich nicht wirklich getestet habe dies scheint jedoch höchst wahrscheinlich) die Leistung zu verbessern; obwohl es sicherlich bei der Suche hilft.

Da Codes nur eine geringe Größe haben - in der Regel nicht mehr als 3 Zeichen für Land und Staat -, können die natürlichen Schlüssel in diesem Szenario als Fremdschlüssel verwendet werden.

Das andere Szenario, in dem Schlüssel von längeren varchar-Werten und möglicherweise von größeren Tabellen abhängig sind; Der Ersatzschlüssel hat wahrscheinlich den Vorteil.

0
Vinod