Prestazioni di indici non cluster su heap e indici cluster


39

Questo Libro bianco del 2007 confronta le prestazioni di singole istruzioni di selezione / inserimento / eliminazione / aggiornamento e selezione intervallo su una tabella organizzata come un indice cluster rispetto a una tabella organizzata come un heap con un indice non cluster sulle stesse colonne chiave dell'elemento della configurazione tavolo.

Generalmente l'opzione dell'indice cluster ha funzionato meglio nei test in quanto vi è una sola struttura da mantenere e perché non sono necessarie ricerche nei segnalibri.

Un caso potenzialmente interessante non trattato dall'articolo sarebbe stato un confronto tra un indice non cluster su un heap rispetto a un indice non cluster su un indice cluster. In quel caso, mi sarei aspettato che l'heap potesse persino funzionare meglio poiché una volta a livello foglia NCI, SQL Server ha un RID da seguire direttamente anziché dover attraversare l'indice cluster.

Qualcuno è a conoscenza di test formali simili che sono stati effettuati in questo settore e, in caso affermativo, quali sono stati i risultati?

Risposte:


41

Per verificare la tua richiesta ho creato 2 tabelle seguendo questo schema:

  • 7,9 milioni di record che rappresentano le informazioni sul saldo.
  • un campo di identità che conta da 1 a 7,9 milioni
  • un campo numerico che raggruppa i record in circa 500.000 gruppi.

La prima tabella chiamata ha heapottenuto un indice non cluster sul campo group. La seconda tabella chiamata ha clustottenuto un indice cluster sul campo sequenziale chiamato keye un indice non cluster sul campogroup

I test sono stati eseguiti su un processore I5 ​​M540 con 2 core hyperthreaded, memoria da 4Gb e Windows 7 a 64 bit.

Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64) 
Apr  2 2010 15:48:46 
Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)  

Aggiornamento del 9 marzo 2011 : ho eseguito un secondo benchmark più ampio eseguendo il seguente codice .net e registrando Durata, CPU, Letture, Scritture e RowCounts in Sql Server Profiler. (Il CommandText utilizzato verrà menzionato nei risultati.)

NOTA: CPU e durata sono espressi in millisecondi

  • 1000 query
  • zero query CPU vengono eliminate dai risultati
  • 0 righe interessate vengono eliminate dai risultati
int[] idList = new int[] { 6816588, 7086702, 6498815 ... }; // 1000 values here.
using (var conn = new SqlConnection(@"Data Source=myserver;Initial Catalog=mydb;Integrated Security=SSPI;"))
            {
                conn.Open();
                using (var cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "select * from heap where common_key between @id and @id+1000"; 
                    cmd.Parameters.Add("@id", SqlDbType.Int);
                    cmd.Prepare();
                    foreach (int id in idList)
                    {
                        cmd.Parameters[0].Value = id;

                        using (var reader = cmd.ExecuteReader())
                        {
                            int count = 0;
                            while (reader.Read())
                            {
                                count++;
                            }
                            Console.WriteLine(String.Format("key: {0} => {1} rows", id, count));
                        }
                    }
                }
            }

Fine dell'aggiornamento il 9 marzo 2011 .

SELEZIONA le prestazioni

Per verificare i numeri di performanc ho eseguito le seguenti query una volta nella tabella heap e una volta nella tabella clust:

select * from heap/clust where group between 5678910 and 5679410
select * from heap/clust where group between 6234567 and 6234967
select * from heap/clust where group between 6455429 and 6455729
select * from heap/clust where group between 6655429 and 6655729
select * from heap/clust where group between 6955429 and 6955729
select * from heap/clust where group between 7195542 and 7155729

I risultati di questo benchmark sono per heap:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  1510  31ms  309ms
401   405   15ms  283ms
2700  2709  0ms   472ms
0     3     0ms   30ms
2953  2962  32ms  257ms
0     0     0ms   0ms

Aggiornamento del 9 marzo 2011 : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 righe hanno> 0 CPU e influiscono su più di 0 righe
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6368         -         
Cpu            15        374      37   0.00754
Reads        1069      91459    7682   1.20155
Writes          0          0       0   0.00000
Duration   0.3716   282.4850 10.3672   0.00180

Fine dell'aggiornamento il 9 marzo 2011 .


per la tabella clusti risultati sono:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  4827  31ms  327ms
401   1241  0ms   242ms
2700  8372  0ms   410ms
0     3     0ms   0ms
2953  9060  47ms  213ms
0     0     0ms   0ms

Aggiornamento del 9 marzo 2011 : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 righe hanno> 0 CPU e influiscono su più di 0 righe
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6056         -
Cpu            15        468      38   0.00782
Reads        3194     227018   20457   3.37618
Writes          0          0       0       0.0
Duration   0.3949   159.6223 11.5699   0.00214

Fine dell'aggiornamento il 9 marzo 2011 .


SELEZIONA CON JOIN performance

cmd.CommandText = "select * from heap/clust h join keys k on h.group = k.group where h.group between @id and @id+1000";


I risultati di questo benchmark sono per heap:

873 righe hanno> 0 CPU e influiscono su più di 0 righe

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1009       4170    1683         -
Cpu            15         47      18   0.01175
Reads        2145       5518    2867   1.79246
Writes          0          0       0   0.00000
Duration   0.8215   131.9583  1.9095   0.00123

I risultati di questo benchmark sono per clust:

865 righe hanno> 0 CPU e influiscono su più di 0 righe

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       4143    1685         -
Cpu            15         47      18   0.01193
Reads        5320      18690    8237   4.97813
Writes          0          0       0   0.00000
Duration   0.9699    20.3217  1.7934   0.00109

Prestazioni AGGIORNAMENTO

Il secondo batch di query sono dichiarazioni di aggiornamento:

update heap/clust set amount = amount + 0 where group between 5678910 and 5679410
update heap/clust set amount = amount + 0 where group between 6234567 and 6234967
update heap/clust set amount = amount + 0 where group between 6455429 and 6455729
update heap/clust set amount = amount + 0 where group between 6655429 and 6655729
update heap/clust set amount = amount + 0 where group between 6955429 and 6955729
update heap/clust set amount = amount + 0 where group between 7195542 and 7155729

i risultati di questo benchmark per heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  3013  31ms  175ms
401   806   0ms   22ms
2700  5409  47ms  100ms
0     3     0ms   0ms
2953  5915  31ms  88ms
0     0     0ms   0ms

Aggiornamento del 9 marzo 2011 : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 righe hanno> 0 CPU e influiscono su più di 0 righe
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5598       811         
Cpu            15        873      56   0.01199
Reads        2080     167593   11809   2.11217
Writes          0       1687     121   0.02170
Duration   0.6705   514.5347 17.2041   0.00344

Fine dell'aggiornamento il 9 marzo 2011 .


i risultati di questo benchmark per clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9126  16ms  35ms
401   2444  0ms   4ms
2700  16385 31ms  54ms
0     3     0ms   0ms 
2953  17919 31ms  35ms
0     0     0ms   0ms

Aggiornamento del 9 marzo 2011 : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853 righe hanno> 0 CPU e influiscono su più di 0 righe
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5420         -
Cpu            15        594      50   0.01073
Reads        6226     432237   33597   6.20450
Writes          0       1730     110   0.01971
Duration   0.9134   193.7685  8.2919   0.00155

Fine dell'aggiornamento il 9 marzo 2011 .


ELIMINA benchmark

il terzo batch di query che ho eseguito sono le istruzioni di eliminazione

delete heap/clust where group between 5678910 and 5679410
delete heap/clust where group between 6234567 and 6234967
delete heap/clust where group between 6455429 and 6455729
delete heap/clust where group between 6655429 and 6655729
delete heap/clust where group between 6955429 and 6955729
delete heap/clust where group between 7195542 and 7155729

Il risultato di questo benchmark per heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  10630 62ms  179ms
401   2838  0ms   26ms
2700  19077 47ms  87ms
0     4     0ms   0ms
2953  20865 62ms  196ms
0     4     0ms   9ms

Aggiornamento del 9 marzo 2011 : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 righe hanno> 0 CPU e influiscono su più di 0 righe
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     192      69788    4781         -
Cpu            15        499      45   0.01247
Reads         841     307958   20987   4.37880
Writes          2       1819     127   0.02648
Duration   0.3775  1534.3383 17.2412   0.00349

Fine dell'aggiornamento il 9 marzo 2011 .


il risultato di questo benchmark per clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9228  16ms  55ms
401   3681  0ms   50ms
2700  24644 46ms  79ms
0     3     0ms   0ms
2953  26955 47ms  92ms
0     3     0ms   0ms

Aggiornamento del 9 marzo 2011 :

cmd.CommandText = "delete clust where group between @id and @id+1000";

  • 751 righe hanno> 0 CPU e influiscono su più di 0 righe
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     144      69788    4648         -
Cpu            15        764      56   0.01538
Reads         989     458467   30207   6.48490
Writes          2       1830     127   0.02694
Duration   0.2938  2512.1968 24.3714   0.00555

Fine dell'aggiornamento il 9 marzo 2011 .


INSERT benchmarks

L'ultima parte del benchmark è l'esecuzione delle istruzioni insert.

inserire in heap / clust (...) valori (...), (...), (...), (...), (...), (...)


Il risultato di questo benchmark per heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     38    0ms   31ms

Aggiornamento del 9 marzo 2011 :

string str = @"insert into heap (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 912 istruzioni hanno> 0 CPU
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -
Cpu            15       2138      25   0.02500
Reads        5212       7069    6328   6.32837
Writes         16         34      22   0.02222
Duration   1.6336   293.2132  4.4009   0.00440

Fine dell'aggiornamento il 9 marzo 2011 .


Il risultato di questo benchmark per clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     50    0ms   18ms

Aggiornamento del 9 marzo 2011 :

string str = @"insert into clust (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 946 istruzioni hanno> 0 CPU
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -      
Cpu            15       2403      21   0.02157
Reads        6810       8997    8412   8.41223
Writes         16         25      19   0.01942
Duration   1.5375   268.2571  6.1463   0.00614

Fine dell'aggiornamento il 9 marzo 2011 .


conclusioni

Sebbene ci siano più letture logiche in corso quando si accede alla tabella con l'indice cluster e non cluster (mentre si utilizza l'indice non cluster), i risultati delle prestazioni sono:

  • Le istruzioni SELECT sono comparabili
  • Le istruzioni UPDATE sono più veloci con un indice cluster inserito
  • Le istruzioni DELETE sono più veloci con un indice cluster inserito
  • Le istruzioni INSERT sono più veloci con un indice cluster inserito

Ovviamente il mio benchmark era molto limitato su un tipo specifico di tabella e con un set molto limitato di query, ma penso che sulla base di queste informazioni possiamo già iniziare a dire che è praticamente sempre meglio creare un indice cluster sulla tabella.

Aggiornamento del 9 marzo 2011 :

Come possiamo vedere dai risultati aggiunti, le conclusioni sui test limitati non erano corrette in tutti i casi.

Durata ponderata

I risultati ora indicano che le uniche istruzioni che beneficiano dell'indice cluster sono le istruzioni di aggiornamento. Le altre istruzioni sono più lente di circa il 30% sulla tabella con indice cluster.

Alcuni grafici aggiuntivi in ​​cui ho tracciato la durata ponderata per query per heap vs clust. Heap di durata ponderato rispetto al cluster per Seleziona

Heap di durata ponderata vs raggruppato per Partecipa

Heap di durata ponderato rispetto al cluster per l'aggiornamento

Heap di durata ponderata vs cluster per Elimina

Come puoi vedere, il profilo delle prestazioni per le istruzioni insert è piuttosto interessante. I picchi sono causati da alcuni punti dati che impiegano molto più tempo per il completamento. Heap di durata ponderato rispetto al cluster per Inserisci

Fine dell'aggiornamento il 9 marzo 2011 .


@Martin Proverò ad eseguirlo su un server con alcune tabelle con 500 milioni di record quando troverò un po 'di tempo la prossima settimana.
Filip De Vos

Dubito della veridicità di questo test. Alcune parti richiedono una seria attenzione, come ad esempio le prestazioni INSERT che affermano che l'indice cluster è più veloce: c'erano più letture nella versione CLUST, ma il tempo trascorso è inferiore. Personalmente avrei ignorato il tempo trascorso entro 10 s di millisecondi (variabilità temporale) - significa meno del conteggio delle letture.


1
@Martin, @Richard, @marc_s. Sto lavorando su un benchmark più serio in questo momento. Spero di poter aggiungere i risultati più tardi oggi.
Filip De Vos,

1
@Filip - Wow! Meriti sicuramente la generosità per tutto il duro lavoro che hai dedicato a questa risposta. Sebbene, come giustamente fai notare, questo è stato un punto di riferimento su un tipo specifico di tabella con un set molto limitato di query e chilometraggio varierà senza dubbio.
Martin Smith,

12

Come Kimberly Tripp - la regina dell'indicizzazione - spiega abbastanza bene nel suo post sul blog The Clustered Index Debate continua ... , avere una chiave di clustering su una tabella di database accelera praticamente tutte le operazioni, non solo SELECT.

SELECT è generalmente più lento su un heap rispetto a una tabella di cluster, purché si scelga una buona chiave di clustering, qualcosa come un INT IDENTITY. Se si utilizza una chiave di cluster davvero pessima, come un GUID o una chiave composta con molti componenti di lunghezza variabile, allora, ma solo allora, un heap potrebbe essere più veloce. Ma in quel caso, devi davvero ripulire il design del tuo database in primo luogo ...

Quindi, in generale, non penso che ci sia alcun punto in un heap: scegli una buona e utile chiave di clustering e dovresti trarne beneficio sotto tutti gli aspetti.


3
Questa è una non risposta. Martin è piuttosto solido su SQL Server; la domanda aveva lo scopo di ottenere risultati verificati verificati nel mondo reale dai test delle prestazioni, non più teoria.

L'articolo di Kimberly Tripp collegato presuppone effettivamente che tutti gli indici non cluster coprano. In tal caso, non ci sarebbero ricerche e il vantaggio dell'heap nelle ricerche verrebbe negato. Tuttavia, non è un mondo in cui la maggior parte di noi vive. Nei nostri casi, provare a progettare tutti o la maggior parte dei nostri indici non cluster per coprire crea problemi a sé stanti.

@ dbaguy52: perché pensi che Kim Tripp supponga che tutti gli indici NC siano coperti? Non vedo alcuna idea di ciò nel suo post sul blog ..... spieghi più in dettaglio cosa ti fa credere che sia il caso (o che è la sua ipotesi)
marc_s

7

Mi è capitato di imbatterti in questo articolo di Joe Chang che affronta questa domanda. Incollate le sue conclusioni di seguito.

Considera una tabella per la quale gli indici hanno una profondità 4, in modo che vi sia un livello radice, 2 livelli intermedi e il livello foglia. L'indice cerca una singola chiave di indice (ovvero nessuna ricerca di chiavi) genererebbe 4 IO logici (LIO). Ora considera se è richiesta una ricerca chiave. Se la tabella ha un indice cluster anche di profondità 4, ogni ricerca di chiavi genera 4 LIO. Se la tabella era un heap, ogni ricerca di chiavi genera 1 LIO. In realtà, la ricerca delle chiavi in ​​un heap è circa il 20-30% in meno di una ricerca delle chiavi in ​​un indice cluster, non vicino al rapporto LIO 4: 1.


1
La cosa interessante da notare è che la citazione di Joe Chang ha identificato un vantaggio in termini di efficienza del 20-30% per i cumuli in base ai suoi presupposti, che è praticamente lo stesso vantaggio identificato nell'aggiornamento dell'articolo del 9 marzo.
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.