Indicizzazione di un GUID PK in SQL Server 2012


13

I miei sviluppatori hanno configurato la loro applicazione per utilizzare GUID come PK per quasi tutte le loro tabelle e per impostazione predefinita SQL Server ha impostato l'indice cluster su questi PK.

Il sistema è relativamente giovane e le nostre tabelle più grandi sono poco più di un milione di righe, ma stiamo esaminando la nostra indicizzazione e vogliamo essere in grado di ridimensionare rapidamente in quanto potrebbe essere necessario nel prossimo futuro.

Quindi, la mia prima inclinazione è stata quella di spostare l'indice cluster nel campo creato che è una rappresentazione bigint di un DateTime. Tuttavia, l'unico modo in cui posso rendere unico il CX sarebbe includere la colonna GUID in questo CX ma ordinarla per prima.

Ciò renderebbe la chiave di clustering troppo ampia e aumenterebbe le prestazioni per le scritture? Anche le letture sono importanti, ma le scritture sono probabilmente una preoccupazione maggiore a questo punto.


1
Come vengono generati i GUID? NEWID o NEWSEQUENTIALID?
swasheck,

6
Le prestazioni guid guid e insert in cluster dovrebbero essere solo in una frase se la parola che precede immediatamente "performance" è minimizzata
billinkc

2
Porta fuori gli sviluppatori a pranzo e spiega loro che se usano di nuovo NEWID () come chiave primaria, incolperai le loro scarse prestazioni. Ti chiederanno molto rapidamente cosa fare per impedirlo. A quel punto, dici invece di usare IDENTITY (1,1). (forse una leggera semplificazione eccessiva ma funzionerà 9 volte su 10).
Max Vernon,

3
La ragione del nostro odio per guid è che sono ampi (16 byte) e quando non creati con newsequentialidsono casuali. Le chiavi raggruppate sono le migliori quando sono strette e in aumento. Un GUID è l'opposto: grasso e casuale. Immagina una libreria quasi piena di libri. Arriva l'OED e, a causa della casualità delle guide, si inserisce nel mezzo dello scaffale. Per mantenere le cose ordinate, la metà giusta dei libri deve essere puntata in una nuova posizione che richiede molto tempo. Questo è ciò che il GUID sta facendo al tuo database e uccide le prestazioni.
billinkc,

7
Il modo per risolvere il problema dell'utilizzo di identificatori univoci è tornare al tavolo da disegno e non utilizzare identificatori univoci . Non sono terribili se il sistema è piccolo, ma se hai almeno qualche milione di tabelle + (o qualsiasi tabella più grande di quella), sei completamente impazzito usando gli identificatori univoci per le chiavi.
Jon Seigel,

Risposte:


20

I problemi principali con i GUID, in particolare quelli non sequenziali, sono:

  • Dimensione della chiave (16 byte contro 4 byte per un INT): questo significa che stai memorizzando 4 volte la quantità di dati nella tua chiave insieme a quello spazio aggiuntivo per tutti gli indici se questo è il tuo indice cluster.
  • Frammentazione dell'indice: è praticamente impossibile mantenere una colonna GUID non sequenziale deframmentata a causa della natura completamente casuale dei valori chiave.

Cosa significa questo per la tua situazione? Dipende dal tuo design. Se il tuo sistema riguarda semplicemente le scritture e non hai dubbi sul recupero dei dati, l'approccio delineato da Thomas K è accurato. Tuttavia, devi tenere presente che perseguendo questa strategia, stai creando molti potenziali problemi per la lettura di questi dati e la loro memorizzazione. Come sottolinea Jon Seigel , occuperai anche più spazio e essenzialmente avrai memoria gonfia.

La domanda principale sui GUID è quanto siano necessari. Agli sviluppatori piacciono perché garantiscono l'unicità globale, ma è una rara occasione in cui questo tipo di unicità è necessario. Ma considera che se il tuo numero massimo di valori è inferiore a 2.147.483.647 (il valore massimo di un intero con segno a 4 byte), probabilmente non stai usando il tipo di dati appropriato per la tua chiave. Anche usando BIGINT (8 byte), il valore massimo è 9.223.372.036.854.775.807. Questo è in genere sufficiente per qualsiasi database non globale (e molti di quelli globali) se è necessario un valore di incremento automatico per una chiave univoca.

Infine, per quanto riguarda l'utilizzo di un heap rispetto a un indice cluster, se si stanno semplicemente scrivendo dati un heap sarebbe più efficiente perché si minimizza il sovraccarico per gli inserti. Tuttavia, gli heap in SQL Server sono estremamente inefficienti per il recupero dei dati. La mia esperienza è stata che un indice cluster è sempre desiderabile se ne hai la possibilità di dichiararlo. Ho visto l'aggiunta di un indice cluster a una tabella (4 miliardi + record) migliorare le prestazioni complessive di selezione di un fattore 6.

Informazioni aggiuntive:


13

Non c'è niente di sbagliato nel GUID come chiavi e cluster in un sistema OLTP (a meno che non ci siano MOLTI indici sulla tabella che soffrono della maggiore dimensione del cluster). È un dato di fatto, sono molto più scalabili delle colonne IDENTITY.

È diffusa la convinzione che i GUID rappresentino un grosso problema in SQL Server - in gran parte, è semplicemente sbagliato. È un dato di fatto, GUID può essere significativamente più scalabile su scatole con più di circa 8 core:

Mi dispiace, ma i tuoi sviluppatori hanno ragione. Preoccupati di altre cose prima di preoccuparti del GUID.

Oh, e infine: perché vuoi un indice cluster in primo luogo? Se la tua preoccupazione è un sistema OLTP con molti piccoli indici, probabilmente stai meglio con un heap.

Consideriamo ora quale frammentazione (che il GUID introdurrà) fa alle tue letture. Esistono tre problemi principali con la frammentazione:

  1. La pagina suddivide gli I / O del disco di costo
  2. Le pagine a metà intere non sono efficienti in termini di memoria delle pagine intere
  3. Fa sì che le pagine vengano archiviate in modo non ordinato, il che rende meno probabile l'I / O sequenziale

Poiché la tua preoccupazione in merito alla questione riguarda la scalabilità, che possiamo definire come "L'aggiunta di altro hardware rende il sistema più veloce", questi sono i minori problemi. Per affrontare ognuno a turno

Annuncio 1) Se si desidera ridimensionare, è possibile acquistare I / O. Anche un SSD Samsung / Intel da 512 GB economico (a pochi USD / GB) ti porterà ben oltre 100.000 IOPS. Non lo consumerai presto in un sistema a 2 socket. E se dovessi imbatterti in questo, comprane uno in più e sei pronto

Annuncio 2) Se cancelli nella tua tabella, avrai comunque metà pagine intere. E anche se non lo fai, la memoria è economica e per tutti tranne i più grandi sistemi OLTP - i dati caldi dovrebbero adattarsi lì. Cercare di raggruppare più dati in pagine è subottimizzato quando si cerca la scala.

Annuncio 3) Una tabella costruita con suddivisione frequente della pagina, dati altamente frammentati esegue I / O casuali esattamente alla stessa velocità di una tabella riempita in sequenza

Per quanto riguarda l'adesione, ci sono due principali tipi di join che è probabile che tu veda in un carico di lavoro come OLTP: Hash e loop. Vediamo ciascuno a turno:

Un hash join: un hash join presuppone che il tavolino venga scansionato e quello più grande venga generalmente cercato. È molto probabile che le tabelle piccole siano in memoria, quindi I / O non è un problema per te. Abbiamo già toccato il fatto che le ricerche hanno lo stesso costo in un indice frammentato che in un indice non frammentato

Loop join: verrà cercato il tavolo esterno. Stesso costo

Potresti anche avere molte scansioni di tabelle in corso, ma il GUID non è di nuovo la tua preoccupazione, l'indicizzazione è corretta.

Ora, potresti avere alcune scansioni di intervallo legittime in corso (specialmente quando si uniscono su chiavi esterne) e in questo caso, i dati frammentati sono meno "compressi" rispetto ai dati non frammentati. Ma consideriamo quali join è probabile che vedrai in dati 3NF ben indicizzati:

  1. Un join da una tabella che ha un riferimento di chiave esterna alla chiave primaria della tabella a cui fa riferimento

  2. Viceversa

Annuncio 1) In questo caso, stai cercando una sola ricerca nella chiave primaria - unendo n a 1. Frammentazione o no, stesso costo (una ricerca)

Annuncio 2) In questo caso, ti stai unendo alla stessa chiave, ma potresti recuperare più di una riga (ricerca intervallo). Il join in questo caso è da 1 a n. Tuttavia, nella tabella esterna che stai cercando, stai cercando la stessa chiave, che ha la stessa probabilità di trovarsi sulla stessa pagina in un indice frammentato rispetto a una non frammentata.

Considera quelle chiavi esterne per un momento. Anche se avevi posato "perfettamente" in sequenza le nostre chiavi primarie, tutto ciò che punta a quella chiave sarà comunque non sequenziale.

Ovviamente, potresti essere in esecuzione su una macchina virtuale in alcune SAN in alcune banche a basso costo e con processi elevati. Quindi tutti questi consigli andranno persi. Ma se questo è il tuo mondo, la scalabilità probabilmente non è ciò che stai cercando - stai cercando prestazioni e alta velocità / costo - che sono entrambe cose diverse.


1
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Paul White 9

5

Thomas: alcuni dei tuoi punti hanno perfettamente senso e sono d'accordo con tutti. Se utilizzi SSD, il saldo di ciò per cui ottimizzi cambia. Casuale vs sequenziale non è la stessa discussione del disco rotante.

Concordo in particolare sul fatto che avere una visione DB pura sia terribilmente sbagliato. Rendere l'applicazione lenta e non scalabile per migliorare solo le prestazioni del DB può essere fuorviante.

Il grosso problema con IDENTITY (o sequenza, o qualsiasi cosa generata nel DB) è che è terribilmente lento in quanto richiede un round trip al DB per creare una chiave, e questo fa automaticamente un collo di bottiglia nel tuo DB, impone che le applicazioni debbano effettuare una chiamata DB per iniziare a utilizzare una chiave. La creazione di un GUID risolve questo problema utilizzando l'applicazione per creare la chiave, è garantito per essere globalmente univoco (per definizione) e i livelli dell'applicazione possono quindi utilizzarlo per passare il record PRIMA di incorrere in un round-trip DB.

Ma tendo a usare un'alternativa ai GUID La mia preferenza personale per un tipo di dati qui è un BIGINT unico a livello globale generato dall'app. Come si fa a fare questo? Nell'esempio più banale, aggiungi una piccola funzione MOLTO leggera alla tua app per ottenere un GUID. Supponendo che la tua funzione hash sia veloce e relativamente veloce (vedi CityHash da Google per un esempio: http://google-opensource.blogspot.in/2011/04/introducing-cityhash.html - assicurati di aver completato tutti i passaggi di compilazione, o la variante FNV1a di http://tools.ietf.org/html/draft-eastlake-fnv-03 per codice semplice) questo ti dà il vantaggio di identificatori univoci generati dall'applicazione e di un valore chiave a 64 bit con cui le CPU funzionano meglio .

Esistono altri modi per generare BIGINT e in entrambi questi algoritmi esiste la possibilità di collisioni tra hash: leggi e prendi decisioni consapevoli.


2
Ti suggerisco di modificare la tua risposta come una risposta alla domanda del PO e non (come è ora) come una risposta alla risposta di Thomas. Puoi ancora evidenziare le differenze tra Thomas (, MikeFal's) e il tuo suggerimento.
ypercubeᵀᴹ

2
Si prega di indirizzare la risposta alla domanda. In caso contrario, lo rimuoveremo per te.
JNK,

2
Grazie per i commenti Marco. Quando modifichi la tua risposta (che a mio avviso fornisce un ottimo contesto) cambierei una cosa: IDENTITÀ non richiede un ulteriore round trip al server se stai attento con INSERT. Puoi sempre restituire SCOPE_IDENTITY () nel batch che chiama INSERT ..
Thomas Kejser

1
Per quanto riguarda "è terribilmente lento in quanto richiede un round trip al DB per creare una chiave" - ​​puoi prenderne quanti ne hai bisogno in un round trip.
AK,

Per quanto riguarda "puoi prenderne quante ne hai bisogno in un round trip" - Non puoi farlo con le colonne IDENTITY o qualsiasi altro metodo in cui stai fondamentalmente usando DEFAULT a livello di database.
Avi Cherry,
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.