Domanda

Esiste una differenza di prestazioni misurabile tra l'utilizzo di INT vs. VARCHAR come chiave primaria in MySQL? Vorrei utilizzare VARCHAR come chiave primaria per gli elenchi di riferimento (pensa agli Stati Uniti, ai codici Paese) e un collega non si sposterà su INT AUTO_INCREMENT come chiave primaria per tutte le tabelle.

Il mio argomento, come dettagliato qui , è che la differenza di prestazioni tra INT e VARCHAR è trascurabile, poiché ogni riferimento a chiave esterna INT richiederà un JOIN per dare un senso al riferimento, una chiave VARCHAR presenterà direttamente le informazioni.

Quindi, qualcuno ha esperienza con questo particolare caso d'uso e le preoccupazioni legate alle prestazioni ad esso associate?

È stato utile?

Soluzione

Hai sottolineato che puoi evitare un certo numero di query unite utilizzando ciò che viene chiamato chiave naturale anziché una chiave surrogata . Solo tu puoi valutare se il vantaggio di questo è significativo nella tua applicazione.

Cioè, puoi misurare le query nella tua applicazione che sono le più importanti per essere veloci, perché funzionano con grandi volumi di dati o vengono eseguite molto frequentemente. Se queste query traggono vantaggio dall'eliminazione di un join e non risentono dell'utilizzo di una chiave primaria varchar, quindi eseguilo.

Non utilizzare nessuna strategia per tutte le tabelle nel database. È probabile che in alcuni casi una chiave naturale sia migliore, ma in altri casi una chiave surrogata è migliore.

Altre persone sottolineano che è raro in pratica che una chiave naturale non cambi mai o abbia duplicati, quindi le chiavi surrogate di solito valgono la pena.

Altri suggerimenti

Non si tratta di prestazioni. Riguarda ciò che rende una buona chiave primaria. Unico e immutabile nel tempo. Potresti pensare che un'entità come un prefisso internazionale non cambi mai nel tempo e sarebbe un buon candidato per una chiave primaria. Ma l'amara esperienza è che raramente è così.

INT AUTO_INCREMENT soddisfa il "unico e immutabile nel tempo" condizione. Da qui la preferenza.

Dipende dalla lunghezza .. Se varchar avrà 20 caratteri e int è 4, quindi se si utilizza un int, l'indice avrà CINQUE volte il numero di nodi per pagina di spazio indice sul disco ... Quello significa che l'attraversamento dell'indice richiederà un quinto del numero di letture fisiche e / o logiche ..

Quindi, se le prestazioni sono un problema, data l'opportunità, usa sempre una chiave integrale non significativa (chiamata surrogata) per le tue tabelle e per le chiavi esterne che fanno riferimento alle righe in queste tabelle ...

Allo stesso tempo , per garantire la coerenza dei dati, ogni tabella in cui è importante dovrebbe anche avere una chiave alternativa non numerica significativa, (o indice univoco) per garantire che le righe duplicate non possano essere inserite (duplicate in base a attributi di tabella significativi).

Per l'uso specifico di cui stai parlando (come le ricerche di stato) non importa davvero perché la dimensione della tabella è così piccola .. In generale non vi è alcun impatto sulle prestazioni degli indici su tabelle con meno di un paio mille righe ...

Assolutamente no.

Ho fatto diversi ... diversi ... controlli delle prestazioni tra INT, VARCHAR e CHAR.

10 milioni di tabelle record con un PRIMARY KEY (univoco e raggruppato) avevano la stessa identica velocità e prestazioni (e costi di sottostruttura) indipendentemente da quale delle tre ho usato.

Detto questo ... usa tutto ciò che è meglio per la tua applicazione. Non preoccuparti per le prestazioni.

Ero un po 'seccato dalla mancanza di benchmark per questo online, quindi ho eseguito un test da solo.

Nota però che non lo faccio regolarmente, quindi controlla la mia configurazione e i passaggi per eventuali fattori che potrebbero aver influenzato i risultati involontariamente e pubblica i tuoi dubbi nei commenti.

L'impostazione era la seguente:

  • Intel & # 174; Nucleo & # 8482; CPU i7-7500U a 2,70 GHz e # 215; 4
  • 15,6 GB di RAM, di cui ho assicurato che circa 8 GB erano liberi durante il test.
  • Unità SSD da 148,6 GB, con molto spazio libero.
  • Ubuntu 16.04 64-bit
  • MySQL Ver 14.14 Distrib 5.7.20, per Linux (x86_64)

Le tabelle:

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;

Quindi, ho riempito 10 milioni di righe in ogni tabella con uno script PHP la cui essenza è così:

$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.';
}

Per le tabelle int , il bit ($ keys [rand (0, 9)]) è stato sostituito con solo rand (0, 9) e per le tabelle varchar , ho usato i nomi completi degli stati USA, senza tagliarli o estenderli a 6 caratteri. generate_random_string () genera una stringa casuale di 10 caratteri.

Quindi ho eseguito MySQL:

  • SET SESSION query_cache_type = 0;
  • Per la tabella jan_int :
    • SELEZIONA conteggio (*) DA jan_int DOVE myindex = 5;
    • SELEZIONA BENCHMARK (1000000000, (SELEZIONA contare (*) DA jan_int DOVE myindex = 5));
  • Per altre tabelle, come sopra, con myindex = 'califo' per le tabelle char e myindex = 'california' per varchar tabelle.

Tempi della query BENCHMARK su ciascuna tabella:

  • 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

Riguardo a table & amp; dimensioni dell'indice, ecco l'output di mostra lo stato della tabella da janperformancetest; (con alcune colonne non mostrate):

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

La mia conclusione è che non ci sono differenze di prestazioni per questo particolare caso d'uso.

Per i codici funzione, probabilmente non c'è differenza. Ciò è particolarmente vero in quanto è probabile che la tabella contenente questi codici sia molto piccola (al massimo un paio di migliaia di righe) e non cambi spesso (quando è l'ultima volta che abbiamo aggiunto un nuovo Stato USA).

Per tabelle più grandi con una variazione più ampia tra i tasti, questo può essere pericoloso. Pensa ad esempio a utilizzare l'indirizzo e-mail / il nome utente da una tabella utente. Cosa succede quando hai pochi milioni di utenti e alcuni di questi utenti hanno nomi lunghi o indirizzi e-mail. Ora, ogni volta che devi unirti a questa tabella usando quella chiave diventa molto più costosa.

Come per la chiave primaria, qualunque cosa renda fisicamente unica una riga dovrebbe essere determinata come chiave primaria.

Per un riferimento come chiave esterna, utilizzare un numero intero con incremento automatico come surrogato è una buona idea per due motivi principali.
 - In primo luogo, di solito si verificano meno spese generali nell'unione.
 - In secondo luogo, se è necessario aggiornare la tabella che contiene il varchar univoco, l'aggiornamento deve passare a cascata a tutte le tabelle figlio e aggiornarle tutte insieme agli indici, mentre con int surrogate, deve solo aggiornare il tabella principale e relativi indici.

Lo svantaggio dell'uso del surrogato è che potresti eventualmente permettere di cambiare il significato del surrogato:

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

Tutto dipende da ciò di cui devi veramente preoccuparti nella tua struttura e da cosa significa di più.

Casi comuni in cui un surrogato AUTO_INCREMENT fa male:

Un modello di schema comune è un mapping molti-a-molti :

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

Le prestazioni di questo modello sono molto migliori, specialmente quando si utilizza InnoDB:

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

Perché?

  • Le chiavi secondarie di InnoDB richiedono una ricerca aggiuntiva; spostando la coppia nel PK, ciò viene evitato per una direzione.
  • L'indice secondario copre "quotazione", quindi non necessita della ricerca aggiuntiva.
  • Questa tabella è più piccola a causa della rimozione di id e di un indice.

Un altro caso ( Paese ):

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

Troppo spesso il novizio normalizza country_code in un INT a 4 byte invece di usare una stringa "naturale" a 2 byte, quasi invariata a 2 byte. Più JOIN più veloci, più piccoli, meno, più leggibili.

In HauteLook, abbiamo modificato molte delle nostre tabelle per utilizzare le chiavi naturali. Abbiamo sperimentato un aumento delle prestazioni nel mondo reale. Come accennato, molte delle nostre query ora utilizzano meno join, il che rende le query più performanti. Useremo anche una chiave primaria composita se ha senso. Detto questo, alcuni tavoli sono più facili da utilizzare se hanno una chiave surrogata.

Inoltre, se si consente alle persone di scrivere interfacce nel proprio database, può essere utile una chiave surrogata. La terza parte può fare affidamento sul fatto che la chiave surrogata cambierà solo in circostanze molto rare.

La domanda riguarda MySQL, quindi dico che c'è una differenza significativa. Se si trattava di Oracle (che memorizza i numeri come stringa - sì, all'inizio non ci potevo credere) quindi non molta differenza.

L'archiviazione nella tabella non è un problema, ma l'aggiornamento e il riferimento all'indice lo sono. Le domande che riguardano la ricerca di un record basato sulla sua chiave primaria sono frequenti: vuoi che si verifichino il più rapidamente possibile perché accadono così spesso.

Il fatto è che una CPU gestisce naturalmente numeri interi a 4 byte e 8 byte, in silicio . È DAVVERO veloce per confrontare due numeri interi - succede in uno o due cicli di clock.

Ora guarda una stringa: è composta da molti caratteri (più di un byte per carattere in questi giorni). Il confronto tra due stringhe per precedenza non può essere eseguito in uno o due cicli. Invece i caratteri delle stringhe devono essere ripetuti fino a quando non viene rilevata una differenza. Sono sicuro che ci sono trucchi per renderlo più veloce in alcuni database, ma questo è irrilevante qui perché un confronto int viene eseguito in modo naturale e velocissimo nel silicio dalla CPU.

La mia regola generale: ogni chiave primaria dovrebbe essere un INT autoincrementante specialmente nelle app OO che usano un ORM (Hibernate, Datanucleus, qualunque cosa) in cui ci sono molte relazioni tra gli oggetti - di solito saranno sempre implementate come un semplice FK e il la capacità del DB di risolverli rapidamente è importante per la tua app " s reattività.

Ho affrontato lo stesso dilemma. Ho realizzato un DW (schema delle costellazioni) con 3 tabelle dei fatti, incidenti stradali, veicoli in incidenti e vittime in incidenti. I dati includono tutti gli incidenti registrati nel Regno Unito dal 1979 al 2012 e 60 tabelle dimensionali. Tutti insieme, circa 20 milioni di dischi.

Relazioni tra tabelle dei fatti:

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

RDMS: MySQL 5.6

In modo nativo l'indice degli incidenti è un varchar (numeri e lettere), con 15 cifre. Ho cercato di non avere chiavi surrogate, una volta che gli indici degli incidenti non sarebbero mai cambiati. In un computer i7 (8 core), il DW è diventato troppo lento per eseguire una query dopo 12 milioni di record di carico a seconda delle dimensioni. Dopo un sacco di rielaborazioni e l'aggiunta di chiavi surrogate bigint ho ottenuto un aumento delle prestazioni della velocità media del 20%. Eppure a basso guadagno prestazionale, ma prova valida. Sto lavorando in MySQL tuning e clustering.

Non sono sicuro delle implicazioni sulle prestazioni, ma sembra un possibile compromesso, almeno durante lo sviluppo, sarebbe includere sia il surrogato "intero auto-incrementato" che " chiave, così come il tuo inteso, unico, "naturale" chiave. Ciò ti darebbe l'opportunità di valutare le prestazioni, così come altri possibili problemi, inclusa la possibilità di cambiare le chiavi naturali.

Come al solito, non ci sono risposte coperte. 'Dipende!' e non sono faceto. La mia comprensione della domanda originale era che le chiavi su piccole tabelle - come Paese (ID intero o codice char / varchar) essendo una chiave esterna per una tabella potenzialmente enorme come la tabella indirizzo / contatto.

Esistono due scenari qui quando si desidera recuperare i dati dal DB. Il primo è un tipo di query elenco / ricerca in cui si desidera elencare tutti i contatti con codici o nomi di stato e paese (gli ID non aiuteranno e quindi avranno bisogno di una ricerca). L'altro è uno scenario get sulla chiave primaria che mostra un singolo record di contatto in cui deve essere mostrato il nome dello stato, il paese.

Per quest'ultimo, probabilmente non importa su cosa si basa l'FK poiché stiamo riunendo tabelle per un singolo record o pochi record e su letture chiave. Il primo scenario (ricerca o elenco) potrebbe essere influenzato dalla nostra scelta. Dal momento che è necessario mostrare il Paese (almeno un codice riconoscibile e forse anche la ricerca stessa include un codice Paese), non dover unire un altro tavolo tramite una chiave surrogata può potenzialmente (sono solo prudente qui perché non ho effettivamente testato questo, ma sembra altamente probabile) migliorare le prestazioni; nonostante il fatto che certamente aiuta con la ricerca.

Poiché i codici sono di piccole dimensioni, in genere non più di 3 caratteri per paese e stato, in questo scenario potrebbe essere opportuno utilizzare le chiavi naturali come chiavi esterne.

L'altro scenario in cui le chiavi dipendono da valori varchar più lunghi e forse da tabelle più grandi; la chiave surrogata ha probabilmente il vantaggio.

Consentitemi di dire sì, c'è sicuramente una differenza, prendendo in considerazione l'ambito delle prestazioni (definizione predefinita):

1- L'uso di surrogate int è più veloce nell'applicazione perché non è necessario utilizzare ToUpper (), ToLower (), ToUpperInvarient () o ToLowerInvarient () nel codice o nella query e queste 4 funzioni hanno benchmark di prestazioni diversi . Vedi le regole di prestazione di Microsoft su questo. (esecuzione dell'applicazione)

2- L'uso di surrogate int garantisce di non modificare la chiave nel tempo. Anche i codici Paese possono cambiare, vedi Wikipedia come i codici ISO sono cambiati nel tempo. Ci vorrebbe molto tempo per cambiare la chiave primaria per i sottotitoli. (esecuzione del mantenimento dei dati)

3- Sembra che ci siano problemi con le soluzioni ORM, come NHibernate quando PK / FK non è int. (rendimento dello sviluppatore)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top