Mysql int vs varchar come chiave primaria (InnoDB Storage Engine?


13

Sto costruendo un'applicazione web (sistema di gestione del progetto) e mi sono chiesto a questo riguardo alle prestazioni.

Ho una tabella dei problemi e al suo interno ci sono 12 chiavi esterne che si collegano a varie altre tabelle. di questi, 8 di cui avrei bisogno di unirmi per ottenere il campo del titolo dagli altri tavoli in modo che il record abbia un senso in un'applicazione web, ma significa quindi fare 8 join che sembrano davvero eccessivi soprattutto perché sto solo inserendo 1 campo per ciascuno di quei join.

Ora mi è stato anche detto di usare una chiave primaria ad incremento automatico (a meno che lo sharding non sia un problema, nel qual caso dovrei usare un GUID) per motivi di permanenza, ma quanto è male usare un varchar (lunghezza massima 32) dal punto di vista delle prestazioni? Voglio dire, la maggior parte di queste tabelle probabilmente non avrà molti record (la maggior parte dovrebbe avere meno di 20 anni). Inoltre, se uso il titolo come chiave primaria, non dovrò più unirmi al 95% delle volte, quindi per il 95% del sql, mi verificherei anche qualsiasi hit di prestazione (penso). L'unico aspetto negativo che mi viene in mente è che ho è che avrò un maggiore utilizzo dello spazio su disco (ma un giorno è davvero un grosso problema).

Il motivo per cui utilizzo tabelle di ricerca per molte cose invece di enum è perché ho bisogno che tutti questi valori siano configurabili dall'utente finale attraverso l'applicazione stessa.

Quali sono gli svantaggi dell'utilizzo di varchar come chiave primaria per una tabella a cui non viene escluso di avere molti record?

AGGIORNAMENTO - Alcuni test

Quindi ho deciso di fare alcuni test di base su queste cose. Ho 100000 record e queste sono le query di base:

Query di base VARCHAR FK

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

Query FK INT di base

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

Ho anche eseguito queste query con le seguenti aggiunte:

  • Seleziona un articolo specifico (dove i.key = 43298)
  • Raggruppa per i.id
  • Ordina per (it.title per int FK, i.issueTypeId per varchar FK)
  • Limite (50000, 100)
  • Raggruppa e limita insieme
  • Raggruppa, ordina e limita insieme

I risultati per questi dove:

TIPO DI QUERY: VARCHAR FK TIME / INT FK TIME


Query di base: ~ 4ms / ~ 52ms

Seleziona un articolo specifico: ~ 140ms / ~ 250ms

Raggruppa per i.id: ~ 4ms / ~ 2.8sec

Ordina per: ~ 231ms / ~ 2sec

Limite: ~ 67ms / ~ 343ms

Raggruppa e limita insieme: ~ 504ms / ~ 2sec

Raggruppa, ordina e limita insieme: ~ 504ms /~2.3sec

Ora non so quale configurazione potrei fare per rendere l'uno o l'altro (o entrambi) più veloci, ma sembra che VARCHAR FK veda più velocemente nelle query per i dati (a volte molto più velocemente).

Immagino di dover scegliere se quel miglioramento di velocità valga la dimensione extra di dati / indice.


Il tuo test indica qualcosa. Testerei anche con varie impostazioni di InnoDB (pool di buffer, ecc.) Perché le impostazioni predefinite di MySQL non sono realmente ottimizzate per InnoDB.
ypercubeᵀᴹ

Dovresti anche testare le prestazioni Inserisci / Aggiorna / Elimina in quanto ciò può essere influenzato anche dalle dimensioni dell'indice. Una chiave cluster di ogni tabella InnoDB è in genere la PK e questa colonna (PK) è inclusa in ogni altro indice. Questo è probabilmente un grande svantaggio di grandi PK in InnoDB e molti indici sul tavolo (ma 32 byte è piuttosto medio, non grande, quindi potrebbe non essere un problema).
ypercubeᵀᴹ

Dovresti anche testare con tabelle più grandi (nell'intervallo di 10-100 M di righe o più grandi), se ti aspetti che i tuoi tavoli possano crescere più di 100K (che non è molto grande).
ypercubeᵀᴹ

@ypercube Quindi aumento i dati a 2 milioni e l'istruzione select per l'FK int diventa più lenta in modo esponenziale dove la chiave esterna varchar rimane piuttosto stabile. Un pensiero che varchar vale il prezzo in termini di requisiti di disco / memoria per il guadagno in query selezionate (che sarà critico su questa particolare tabella e pochi altri).
Ryanzec,

Controlla anche le tue impostazioni db (e in particolare InnoDB), prima di arrivare alle conclusioni. Con piccole tabelle di riferimento, non mi aspetto un aumento esponenziale
ypercubeᵀᴹ

Risposte:


9

Seguo le seguenti regole per le chiavi primarie:

a) Non dovrebbero avere alcun significato commerciale - dovrebbero essere totalmente indipendenti dall'applicazione che stai sviluppando, quindi scelgo numeri interi generati automaticamente. Tuttavia, se è necessario che le colonne aggiuntive siano univoche, creare indici univoci per supportarlo

b) Dovrebbe funzionare in join - l'unione a varchars vs numeri interi è circa da 2 a 3 volte più lenta man mano che la lunghezza della chiave primaria aumenta, quindi si desidera avere le chiavi come numeri interi. Dal momento che tutti i sistemi informatici sono binari, sospetto che la sua stringa sia cambiata in binario quindi confrontata con le altre che è molto lenta

c) Usa il tipo di dati più piccolo possibile - se ti aspetti che la tua tabella abbia pochissime colonne con 52 stati USA, usa il tipo più piccolo possibile forse un CHAR (2) per il codice a 2 cifre, ma continuerei a cercare un minuscolo (128) per la colonna contro un big int che può arrivare a 2 miliardi

Inoltre, avrai una sfida a cascata le tue modifiche dalle chiavi primarie alle altre tabelle se, ad esempio, cambia il nome del progetto (che non è raro)

Scegli interi incrementali automatici sequenziali per le tue chiavi primarie e ottieni le efficienze integrate che i sistemi di database forniscono supporto per le modifiche in futuro


1
Le stringhe non vengono modificate in binarie; sono archiviati in binario dall'inizio. In quale altro modo sarebbero stati memorizzati? Forse stai pensando alle operazioni per consentire un confronto senza distinzione tra maiuscole e minuscole?
Jon of All Trades

6

Nei tuoi test non stai confrontando la differenza di prestazione tra varchar e int key, ma piuttosto il costo di più join. Non sorprende che interrogare 1 tabella sia più veloce che unire molte tabelle.
Un aspetto negativo della chiave primaria varchar è l'aumento della dimensione dell'indice, come sottolineato da atxdba . Anche se la tua tabella di ricerca non ha altri indici tranne PK (che è abbastanza improbabile, ma possibile), ogni tabella che fa riferimento alla ricerca avrà un indice su questa colonna.
Un'altra cosa negativa delle chiavi primarie naturali è che il loro valore può cambiare e che causa molti aggiornamenti a cascata. Non tutti i RDMS, ad esempio Oracle, ti permettono persino di averloon update cascade. In generale, la modifica del valore della chiave primaria considera una cattiva pratica. Non voglio dire che le chiavi primarie naturali siano sempre malvagie; se i valori di ricerca sono piccoli e non cambiano mai penso che potrebbe essere accettabile.

Un'opzione che potresti prendere in considerazione è implementare la vista materializzata. Mysql non lo supporta direttamente, ma è possibile ottenere la funzionalità desiderata con trigger su tabelle sottostanti. Quindi avrai una tabella che ha tutto ciò che ti serve per visualizzare. Inoltre, se le prestazioni sono accettabili, non lottare con il problema che al momento non esiste.


3

Il più grande svantaggio è la ripetizione del PK. Hai sottolineato un aumento dell'utilizzo dello spazio su disco ma, per essere chiari, la maggiore dimensione dell'indice è la tua maggiore preoccupazione. Poiché innodb è un indice cluster, ogni indice secondario memorizza internamente una copia del PK che utilizza per trovare i record corrispondenti.

Dici che le tabelle dovrebbero essere "piccole" (20 righe sono davvero minuscole). Se hai abbastanza RAM per impostare innodb_buffer_pool_size uguale a

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

Quindi fallo e probabilmente starai seduto abbastanza. Come regola generale, tuttavia, si desidera lasciare almeno il 30% - 40% della memoria di sistema totale per altri overhead mysql e dis cache. E questo presuppone che sia un server DB dedicato. Se hai altre cose in esecuzione sul sistema, dovrai prendere in considerazione anche i loro requisiti.


1

Oltre alla risposta @atxdba - che ti ha spiegato perché usare il numerico sarebbe meglio per lo spazio su disco, volevo aggiungere due punti:

  1. Se la tua tabella dei problemi è basata su VARCHAR FK e supponiamo che tu abbia 20 piccoli VARCHAR (32) FK, il tuo record può arrivare a una lunghezza di 20x32 byte, mentre come menzionato le altre tabelle sono tabelle di ricerca, quindi INT FK potrebbe essere TINYINT FK che rende per 20 campi registra 20 byte. So che per diverse centinaia di dischi non cambierà molto, ma quando arriverai a diversi milioni credo che apprezzerai il risparmio di spazio

  2. Per quanto riguarda la velocità, prenderei in considerazione l'utilizzo degli indici di copertura, poiché sembra che per questa query non si stia recuperando una tale quantità di dati dalle tabelle di ricerca, vorrei utilizzare l'indice di copertura e ripetere il test fornito con VARCHAR FK / W / COVERING INDICE E INT FK regolare.

Spero che possa aiutare

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.