Frage

Gibt es eine messbare Leistungsdifferenz zwischen INT vs. VARCHAR als Primärschlüssel in MySQL? Ich möchte für Referenzlisten VARCHAR als Primärschlüssel verwenden (man denke an den US-Staaten, Ländercodes) und einem Mitarbeiter auf der INT AUTO_INCREMENT als Primärschlüssel für alle Tabellen nicht von der Stelle.

Mein Argument, wie detaillierte , ist, dass der Performance-Unterschied zwischen INT und VARCHAR vernachlässigbar ist, da jede INT Fremdschlüssel-Referenz wird ein JOIN erfordert Sinn der Referenz zu machen, ein VARCHAR Schlüssel wird die Informationen direkt präsentieren.

Also, hat jemand Erfahrung mit diesem speziellen Anwendungsfall und den Leistungsproblemen im Zusammenhang mit ihm?

War es hilfreich?

Lösung

Sie machen einen guten Punkt, dass Sie eine bestimmte Anzahl von verbundenen Abfragen vermeiden können mit, was einen genannten natürliche Schlüssel anstelle eines Ersatzschlüssel . Nur können Sie beurteilen, ob der Nutzen dieses in Ihrer Anwendung von Bedeutung ist.

Das heißt, können Sie die Abfragen in Ihrer Anwendung messen, die am wichtigsten zu sein zügigen sind, weil sie mit großen Datenmengen arbeiten oder sie werden sehr häufig ausgeführt. Wenn diese Abfragen aus der Beseitigung einer Verknüpfung profitieren, und leidet nicht einen varchar Primärschlüssel durch verwenden, dann tun Sie es.

Sie entweder nicht für alle Tabellen in Ihrer Datenbank verwenden Strategie. Es ist wahrscheinlich, dass in einigen Fällen ein natürlicher Schlüssel ist besser, aber in anderen Fällen sind ein Ersatzschlüssel ist besser.

Andere Leute einen guten Punkt, dass es in der Praxis selten ist für einen natürlichen Schlüssel niemals Duplikate zu ändern oder hat, so Ersatzschlüssel sind in der Regel lohnt mich.

Andere Tipps

Es geht nicht um Leistung. Es geht darum, was einen guten Primärschlüssel macht. Einzigartig und unveränderlich über die Zeit. Sie können ein Unternehmen wie zum Beispiel eines Ländercode nie denken, im Laufe der Zeit verändert und wäre ein guter Kandidat für einen Primärschlüssel sein. Aber bittere Erfahrung ist, dass selten der Fall ist.

INT AUTO_INCREMENT erfüllt die „einzigartige und unveränderliche im Laufe der Zeit“ Zustand. Daraus ergibt sich die Präferenz.

Abhängig von der Länge .. Wenn der varchar 20 Zeichen lang sein wird, und die int 4, dann, wenn Sie einen int verwenden, Ihr Index fünf Mal so viele Knoten pro Seite des Indexraumes auf der Festplatte haben wird ... Die bedeutet, dass der Index durchlaufen als ein Fünftel erfordern viele physikalische und / oder logische liest ..

Also, wenn Leistung ein Problem, die Möglichkeit gegeben, immer einen integralen nicht-sinnvollen Schlüssel verwenden für Ihre Tabellen (ein Surrogat genannt) und für Fremdschlüssel, die die Zeilen in diesen Tabellen verweisen ...

Zur gleichen Zeit , um die Datenkonsistenz zu gewährleisten, jede Tabelle, wo es darauf ankommt, sollte auch habe einen sinnvollen nicht-numerischen alternativen Schlüssel, (oder ein eindeutiger Index), dass doppelte Zeilen, um sicherzustellen, kann nicht eingefügt werden (doppelten basierend auf sinnvollen Tabellenattributen).

Für die spezifische Verwendung Sie sprechen (wie Zustand Lookups) es spielt wirklich keine Rolle, weil die Größe der Tabelle so klein ist .. In der Regel gibt es keine Auswirkungen auf die Leistung von Indizes für Tabellen mit weniger als ein paar tausend Zeilen ...

Absolut nicht.

Ich habe getan, einige ... mehr ... Leistungsüberprüfungen zwischen INT, VARCHAR und CHAR.

10 Millionen Rekord Tabelle mit einem Primärschlüssel (eindeutig und geclustert) hatte genau die gleiche Geschwindigkeit und Leistung (und Teilbaum Kosten) unabhängig davon, welche der drei I verwendet wird.

Dass gesagt wird ... verwenden, was am besten für Ihre Anwendung ist. Sorgen Sie sich nicht über die Leistung.

Ich war ein wenig verärgert durch den Mangel an Benchmarks für diesen online, so lief ich mich einen Test.

Beachten Sie aber, dass ich es auf einer regelmäßigen Grund nicht tun, so wenden Sie sich bitte mein Setup und Schritte für alle Faktoren überprüfen, die die Ergebnisse ungewollt beeinflusst haben könnten, und posten Sie Ihre Bedenken in den Kommentaren.

Das Setup war wie folgt:

  • Intel® Core ™ i7-7500U CPU @ 2.70GHz × 4
  • 15,6 GiB RAM, von denen ich sicher um 8 GB im Test frei war.
  • 148,6 GB SSD-Laufwerk, mit viel Freiraum.
  • Ubuntu 16.04 64-Bit
  • MySQL Ver 14.14 Distrib 5.7.20 für Linux (x86_64)

Die Tabellen:

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 ist wie folgt:

$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)]) mit nur rand(0, 9) ersetzt und für varchar Tabellen, habe ich volle US-Staat Namen, ohne zu schneiden oder sie zu 6 Zeichen erstrecken. generate_random_string() erzeugt ein 10-Zeichen-zufällige Zeichenfolge.

Dann lief ich in MySQL:

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

Die Zeiten der BENCHMARK Abfrage auf jede Tabelle:

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

In Bezug auf Tabelle & Indexgrößen, hier ist die Ausgabe von show table status from janperformancetest; (w / einigen Spalten nicht dargestellt):

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Meine Schlussfolgerung ist, dass kein Unterschied in der Leistung für diesen speziellen Anwendungsfall gibt.

Für kurze Codes, gibt es wahrscheinlich keinen Unterschied. Dies gilt insbesondere, da die Tabelle diese Codes hält, ist wahrscheinlich sehr klein sein (ein paar tausend Zeilen höchstens) und nicht oft ändern (wenn das letzte Mal ist, dass wir einen neuer US-Bundesstaat hinzugefügt).

Für größere Tabellen mit einer größeren Variation zwischen dem Key, kann dies gefährlich sein. Denken Sie über die Verwendung von E-Mail-Adresse / Benutzername aus einer Benutzertabelle, zum Beispiel. Was passiert, wenn man ein paar Millionen Nutzer haben und einige dieser Nutzer haben lange Namen oder E-Mail-Adressen. Nun kann jede Zeit, die Sie brauchen, um diese Tabelle zu verbinden, dass die Taste es viel teurer wird.

Wie für Primärschlüssel, was physisch eine Reihe einzigartig macht sollte als Primärschlüssel festgelegt werden.

Für eine Referenz als Fremdschlüssel, ein Auto mit Integer als Surrogat Inkrementieren ist eine nette Idee, aus zwei Gründen.
 - Erstens ist es weniger Aufwand in der Regel in der Join entstehen
.  - Zweitens, wenn Sie die Tabelle aktualisieren, die die einzigartige varchar enthält dann das Update auf alle untergeordneten Tabellen kaskadieren hat nach unten und alle von ihnen sowie die Indizes aktualisiert werden, während bei der int Surrogat, ist es nur die aktualisieren hat Master-Tabelle und die Indizes.

Die drawaback die Leihmutter ist, dass Sie möglicherweise Ändern der Bedeutung des Surrogat ermöglichen:

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, was Sie wirklich über in Ihrer Struktur müssen sich Sorgen zu machen und was bedeutet die meisten.

Häufige Fälle, in denen ein Surrogat AUTO_INCREMENT weh tut:

Ein gemeinsames Schema Muster ist ein many-to-many-Mapping :

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, vor allem 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 Lookup; indem das Paar in die PK zu bewegen, das heißt für eine Richtung vermieden.
  • Der Sekundärindex wird als „Abdeckung“, so dass es nicht die zusätzliche Lookup benötigt.
  • Diese Tabelle ist kleiner, weil die von id und einem Index loszuwerden.

Ein weiterer Fall ( Land ):

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

Allzu oft die Anfänger normalisieren country_code in einen 4-Byte-INT anstelle der Verwendung eines 'natürlichen' 2-Byte, nahezu unveränderlich 2-Byte-String. Schneller, kleiner, weniger JOIN, besser lesbar.

Bei Hautelook, änderten wir viele unserer Tabellen natürliche Schlüssel zu verwenden. Wir haben erfahren eine reale Steigerung der Leistung. Wie Sie erwähnt, verwenden viele unserer Abfragen jetzt weniger beitritt, das die Abfragen performanter macht. Wir werden auch einen zusammengesetzten Primärschlüssel verwenden, wenn es Sinn macht. Davon abgesehen, einige Tische sind einfach leichter zu handhaben, wenn sie einen Ersatzschlüssel haben.

Auch wenn Sie lassen die Menschen Schnittstellen zu Ihrer Datenbank zu schreiben, kann ein Ersatzschlüssel hilfreich sein. Die dritte Partei kann sich darauf verlassen, dass der Ersatzschlüssel nur in sehr seltenen Fällen ändern.

Die Frage ist, über MySQL so sagen, dass ich es einen signifikanten Unterschied. Wenn es um Oracle ist. (Die Zahlen als String speichert - ja, ich kann es nicht zuerst glauben), dann nicht viel Unterschied

Die Lagerung in der Tabelle ist nicht das Problem, sondern die Aktualisierung und unter Bezugnahme auf den Index. Abfragen Aufsuchen eines Rekord Beteiligung auf der Grundlage ihrer Primärschlüssel sind häufig - Sie wollen, dass sie so schnell wie möglich kommen, weil sie so oft passieren

.

Die Sache ist ein CPU beschäftigt sich mit 4 Byte und 8 Byte ganze Zahlen natürlich in Silizium . Es ist wirklich schnell für sie zwei ganze Zahlen vergleichen - es in einem oder zwei Taktzyklen passiert.

Jetzt in einem String finden - es besteht aus vielen Zeichen (mehr als ein Byte pro Zeichen in diesen Tagen). Vergleicht man zwei Saiten für Vorrang kann nicht in einem oder zwei Zyklen durchgeführt werden. Stattdessen müssen die Zeichen Strings wiederholt werden, bis ein Unterschied gefunden wird. Ich bin sicher, es gibt Tricks es schneller in einigen Datenbanken zu machen, aber das ist hier nicht relevant, da ein int Vergleich schnell natürlich und Blitz in Silizium erfolgt durch die CPU.

Meine allgemeine Regel - jeder Primärschlüssel sollte ein selbstinkrementierende INT besonders in OO sein apps mit Hilfe eines ORM (Hibernate, Datanucleus, was auch immer), wo es viele Beziehungen zwischen Objekten ist - sie werden in der Regel immer als einfache FK umgesetzt werden und die Fähigkeit für die DB jener schnell zu lösen, ist wichtig, um Ihren App‘ Reaktionsverhalten.

stand ich vor dem gleichen Dilemma. Ich habe eine DW (Constellation-Schema) mit 3 Faktentabellen, Verkehrsunfälle, Fahrzeuge in Unfälle und Verluste bei Unfällen. Die Daten sind alle in UK registrierten Unfälle 1979-2012 und 60 Maßtabellen. Insgesamt rund 20 Millionen Datensätze.

Faktentabellen Beziehungen:

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

RDMS: MySQL 5.6

Nativ der Unfallindex ist ein varchar (Zahlen und Buchstaben), mit 15 Ziffern. Ich versuchte, nicht Ersatzschlüssel zu haben, wenn der Unfall Indizes würde sie nie ändern. In einem i7 (8 Kerne) Computer wurde die DW zu langsam nach 12 Millionen Datensätze von Last abzufragen der Dimensionen abhängig. Nach vielen Nacharbeiten und das Hinzufügen von Bigint Ersatzschlüssel bekam ich eine durchschnittliche Leistungssteigerung von 20% Geschwindigkeit. Doch zu niedrigem Leistungsgewinn, aber gültigen Versuch. Ich arbeite in MySQL Tuning und Clustering.

Nicht sicher über die Auswirkungen auf die Leistung, aber es scheint, einen möglichen Kompromiss, zumindest während der Entwicklung, sowohl die Auto-erhöht, integer „Surrogat“ -Taste sowie beabsichtigten, einzigartig, „natürliche“ Schlüssel zu schließen wäre . Dies würde Ihnen die Möglichkeit, die Leistung zu bewerten, sowie andere mögliche Probleme, einschließlich der Veränderlichkeit der natürlichen Tasten.

Wie üblich, gibt es keine Decke Antworten. 'Es hängt davon ab, ob!' und ich bin nicht spöttisch zu sein. Mein Verständnis für die ursprüngliche Frage war für Schlüssel auf kleinen Tischen -. Wie Land (integer ID oder char / varchar-Code) ist ein Fremdschlüssel zu einer potenziell großen Tisch wie Adress- / Kontakttabelle

Es gibt zwei Szenarien hier, wenn Sie Daten wieder aus dem DB wollen. Zunächst ist eine Liste / Suche Art der Abfrage, wo Sie alle Kontakte mit staatlichen und Ländercodes oder Namen auflisten möchten (ids wird nicht helfen, und benötigen daher eine Lookup). Das andere ist ein get-Szenario auf Primärschlüssel, die einen einzelnen Kontaktdatensatz zeigt, wo der Name des Staates, Landes gezeigt werden muss.

Für letztere bekommen, es ist wahrscheinlich keine Rolle, was die FK auf beruht, da wir die Zusammen Tabellen für einen einzelnen Datensatz oder ein paar Aufzeichnungen und auf Schlüssel liest. Die ehemalige (Suche oder Liste) Szenario kann durch unsere Wahl beeinflusst werden. Da es erforderlich ist, Land (zumindest einen erkennbaren Code und vielleicht sogar die Suche selbst enthält einen Ländercode) zu zeigen, mit nicht kann möglicherweise ein andere Tabelle durch einen Ersatzschlüssel zu kommen (ich bin nur hier zu sein vorsichtig, weil ich nicht wirklich getestet dies, scheint aber sehr wahrscheinlich) die Leistung zu verbessern; ungeachtet der Tatsache, dass es auf jeden Fall bei der Suche hilft.

Als Codes sind klein - nicht mehr als 3 Zeichen in die Regel für Land und Staat, ist es in Ordnung sein kann, die natürlichen Schlüssel als Fremdschlüssel in diesem Szenario zu verwenden,

.

Das andere Szenario, in dem Schlüssel auf mehr varchar Werten abhängig ist und vielleicht auf größeren Tabellen; der Ersatzschlüssel wahrscheinlich hat den Vorteil.

Lassen Sie mich ja sagen, es ist definitiv ein Unterschied, unter Berücksichtigung der Leistungsumfang (Out of the box Definition):

1- Surrogat int Verwendung ist in der Anwendung schneller, weil Sie müssen in Ihrem Code nicht verwenden ToUpper (), ToLower (), ToUpperInvarient () oder ToLowerInvarient () oder in Ihrer Abfrage, und diese vier Funktionen unterschiedliches Performance-Benchmarks haben . Siehe Microsoft Performance Regeln zu diesem Thema. (Leistung der Anwendung)

2- Mit Surrogat int garantiert den Schlüssel nicht im Laufe der Zeit ändern. Auch kann Ländercodes ändern, siehe Wikipedia wie ISO-Codes im Laufe der Zeit verändert. Das würde viel Zeit in Anspruch nehmen, die Primärschlüssel für Unterstrukturen zu ändern. (Leistung der Datenpflege)

3 Es scheint, gibt es Probleme mit ORM Lösungen wie NHibernate, wenn PK / FK int nicht. (Entwicklerleistung)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top