Remus ha utilmente sottolineato che la lunghezza massima della VARCHAR
colonna influisce sulla dimensione della riga stimata e quindi sulla memoria che fornisce SQL Server.
Ho cercato di fare un po 'più di ricerca per ampliare la parte della sua risposta "da questo su cose a cascata". Non ho una spiegazione completa o concisa, ma ecco quello che ho trovato.
Script di riproduzione
Ho creato uno script completo che genera un set di dati falso su cui la creazione dell'indice impiega circa 10 volte più tempo sul mio computer per la VARCHAR(256)
versione. I dati utilizzati è esattamente lo stesso, ma la prima tabella utilizza le effettive lunghezze massime di 18
, 75
, 9
, 15
, 123
, e 5
, mentre tutte le colonne utilizzano una lunghezza massima di 256
nella seconda tabella.
Digitare la tabella originale
Qui vediamo che la query originale si completa in circa 20 secondi e le letture logiche sono uguali alla dimensione della tabella di ~1.5GB
(195K pagine, 8K per pagina).
-- CPU time = 37674 ms, elapsed time = 19206 ms.
-- Table 'testVarchar'. Scan count 9, logical reads 194490, physical reads 0
CREATE CLUSTERED INDEX IX_testVarchar
ON dbo.testVarchar (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO
Digitazione della tabella VARCHAR (256)
Per la VARCHAR(256)
tabella, vediamo che il tempo trascorso è aumentato drammaticamente.
È interessante notare che né il tempo della CPU né le letture logiche aumentano. Ciò ha senso dato che la tabella ha gli stessi dati esatti, ma non spiega perché il tempo trascorso sia molto più lento.
-- CPU time = 33212 ms, elapsed time = 263134 ms.
-- Table 'testVarchar256'. Scan count 9, logical reads 194491
CREATE CLUSTERED INDEX IX_testVarchar256
ON dbo.testVarchar256 (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO
Statistiche I / O e wait: originali
Se acquisiamo un po 'più di dettagli (usando p_perfMon, una procedura che ho scritto ), possiamo vedere che la maggior parte dell'I / O viene eseguita sul LOG
file. Vediamo una quantità relativamente modesta di I / O sull'effettivo ROWS
(il file di dati principale) e il tipo di attesa principale è LATCH_EX
, indicando una contesa di pagine in memoria.
Possiamo anche vedere che il mio disco rotante si trova tra "cattivo" e "tremendamente cattivo", secondo Paul Randal :)
Statistiche I / O e wait: VARCHAR (256)
Per la VARCHAR(256)
versione, le statistiche I / O e wait sembrano completamente diverse! Qui vediamo un enorme aumento dell'I / O sul file di dati ( ROWS
), e i tempi di stallo ora fanno di Paul Randal semplicemente dire "WOW!".
Non sorprende che il tipo di attesa n. 1 sia ora IO_COMPLETION
. Ma perché viene generato così tanto I / O?
Piano di query effettivo: VARCHAR (256)
Dal piano di query, possiamo vedere che l' Sort
operatore ha una fuoriuscita ricorsiva (5 livelli di profondità!) Nella VARCHAR(256)
versione della query. (Non c'è alcun versamento nella versione originale.)
Avanzamento query live: VARCHAR (256)
Possiamo usare sys.dm_exec_query_profiles per visualizzare l'avanzamento delle query live in SQL 2014+ . Nella versione originale, l'intero Table Scan
e Sort
vengono elaborati senza versamenti ( spill_page_count
rimane 0
tutto).
Nella VARCHAR(256)
versione, tuttavia, possiamo vedere che gli spargimenti di pagina si accumulano rapidamente per l' Sort
operatore. Ecco un'istantanea dell'avanzamento della query appena prima del completamento della query. I dati qui sono aggregati su tutti i thread.
Se scavo individualmente in ogni thread, vedo che 2 thread completano l'ordinamento in circa 5 secondi (@ 20 secondi in totale, dopo 15 secondi trascorsi nella scansione della tabella). Se tutti i thread VARCHAR(256)
avessero progredito a questa velocità, la creazione dell'indice sarebbe stata completata all'incirca nello stesso momento della tabella originale.
Tuttavia, i restanti 6 thread procedono a una velocità molto più lenta. Ciò può essere dovuto al modo in cui la memoria viene allocata e al modo in cui i thread vengono trattenuti dall'I / O mentre si riversano dati. Non lo so per certo però.
Cosa sai fare?
Esistono diverse cose che potresti considerare di provare:
- Collaborare con il fornitore per ripristinare una versione precedente. Se ciò non è possibile, lascia che il fornitore non sia soddisfatto di questa modifica in modo che possano prendere in considerazione la possibilità di ripristinarla in una versione futura.
- Quando aggiungi il tuo indice, considera l'utilizzo di
OPTION (MAXDOP X)
dove X
c'è un numero inferiore rispetto all'impostazione attuale a livello di server. Quando ho usato OPTION (MAXDOP 2)
questo specifico set di dati sul mio computer, la VARCHAR(256)
versione è stata completata in 25 seconds
(rispetto a 3-4 minuti con 8 thread!). È possibile che il comportamento di fuoriuscita sia aggravato da un parallelismo più elevato.
- Se è possibile un investimento hardware aggiuntivo, profilare l'I / O (il probabile collo di bottiglia) sul proprio sistema e considerare l'utilizzo di un SSD per ridurre la latenza dell'I / O sostenuta dagli sversamenti.
Ulteriori letture
Paul White ha un bel post sul blog all'interno di SQL Server che potrebbe essere interessante. Parla un po 'di spargimento, inclinazione del thread e allocazione della memoria per tipi paralleli.