Wydajność indeksów nieklastrowanych na stosach a indeksów klastrowanych

39

W białej księdze z 2007 r. Porównano wydajność poszczególnych instrukcji select / insert / delete / update i range select w tabeli zorganizowanej jako indeks klastrowy w porównaniu z tabelą zorganizowaną jako sterta z indeksem nieklastrowanym w tych samych kolumnach kluczowych co element CI stół.

Zasadniczo opcja indeksu klastrowego działała lepiej w testach, ponieważ istnieje tylko jedna struktura do utrzymania i ponieważ nie ma potrzeby wyszukiwania zakładek.

Jednym potencjalnie interesującym przypadkiem nieobjętym dokumentem byłoby porównanie indeksu nieklastrowego na stercie z indeksem nieklastrowanym na indeksie klastrowym. W takim przypadku spodziewałbym się, że sterty mogą nawet działać lepiej, ponieważ raz na poziomie liści NCI SQL Server ma identyfikator RID do śledzenia bezpośrednio, zamiast konieczności przechodzenia przez indeks klastrowany.

Czy ktoś wie o podobnych formalnych testach przeprowadzonych w tym obszarze, a jeśli tak, jakie były wyniki?

Martin Smith
źródło

Odpowiedzi:

41

Aby sprawdzić twoje zapytanie, stworzyłem 2 tabele według tego schematu:

  • 7,9 miliona rekordów reprezentujących informacje o saldzie.
  • pole tożsamości liczące od 1 do 7,9 miliona
  • pole liczbowe grupujące rekordy w około 500 tys. grup.

Pierwsza tabela o nazwie heapma indeks nieklastrowany na polu group. Druga tabela o nazwie clustma indeks klastrowany w wywoływanym polu sekwencyjnym keyi indeks nieklastrowany w polugroup

Testy przeprowadzono na procesorze I5 M540 z 2 rdzeniami hiperwątkowymi, pamięcią 4 Gb i 64-bitowym systemem Windows 7.

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)  

Aktualizacja w dniu 9 marca 2011 r . : Zrobiłem drugi bardziej obszerny test porównawczy, uruchamiając następujący kod .net i rejestrując czas trwania, procesor, odczyt, zapis i liczbę wierszy w Sql Server Profiler. (Użyty tekst CommandText zostanie wymieniony w wynikach).

UWAGA: Procesor i czas trwania są wyrażone w milisekundach

  • 1000 zapytań
  • zero zapytań CPU jest eliminowanych z wyników
  • 0 wierszy, których to dotyczy, są eliminowane z wyników
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));
                        }
                    }
                }
            }

Koniec aktualizacji 9 marca 2011 r .

WYBIERZ wydajność

Aby sprawdzić numery performanc, wykonałem następujące zapytania raz w tabeli stosu i raz w tabeli klastrów:

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

Wyniki tego testu porównawczego dotyczą 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

Aktualizacja w dniu 9 marca 2011 r . : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 Rzędy mają> 0 CPU i wpływają na więcej niż 0 rzędów
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

Koniec aktualizacji 9 marca 2011 r .


dla tabeli clustwyniki są następujące:

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

Aktualizacja w dniu 9 marca 2011 r . : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 Rzędy mają> 0 CPU i wpływają na więcej niż 0 rzędów
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

Koniec aktualizacji 9 marca 2011 r .


WYBIERZ Z POŁĄCZENIEM

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


Wyniki tego testu porównawczego dotyczą heap:

873 Rzędy mają> 0 CPU i wpływają na więcej niż 0 wierszy

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

Wyniki tego testu porównawczego dotyczą clust:

865 wierszy ma> 0 procesorów i wpływa na więcej niż 0 wierszy

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

AKTUALIZACJA wydajności

Druga partia zapytań to instrukcje aktualizacji:

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

wyniki tego testu porównawczego dla 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

Aktualizacja w dniu 9 marca 2011 r . : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 Rzędy mają> 0 CPU i wpływają na więcej niż 0 wierszy
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

Koniec aktualizacji 9 marca 2011 r .


wyniki tego testu porównawczego dla 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

Aktualizacja w dniu 9 marca 2011 r . : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853 Rzędy mają> 0 CPU i wpływają na więcej niż 0 rzędów
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

Koniec aktualizacji 9 marca 2011 r .


USUŃ testy porównawcze

trzecią partią zapytań, które uruchomiłem, są instrukcje usuwania

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

Wynik tego testu porównawczego dla 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

Aktualizacja w dniu 9 marca 2011 r . : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 Rzędy mają> 0 CPU i wpływają na więcej niż 0 rzędów
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

Koniec aktualizacji 9 marca 2011 r .


wynik tego wskaźnika dla 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

Aktualizacja w dniu 9 marca 2011 r . :

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

  • 751 Rzędy mają> 0 CPU i wpływają na więcej niż 0 rzędów
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

Koniec aktualizacji 9 marca 2011 r .


Testy INSERT

Ostatnią częścią testu porównawczego jest wykonanie instrukcji insert.

wstaw do wartości sterty / klastra (...) (...), (...), (...), (...), (...), (...)


Wynik tego testu porównawczego dla heap:

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

Aktualizacja w dniu 9 marca 2011 r . :

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

Koniec aktualizacji 9 marca 2011 r .


Wynik tego testu porównawczego dla clust:

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

Aktualizacja w dniu 9 marca 2011 r . :

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

Koniec aktualizacji 9 marca 2011 r .


Wnioski

Chociaż podczas uzyskiwania dostępu do tabeli za pomocą indeksu klastrowanego i nieklastrowanego (podczas korzystania z indeksu nieklastrowanego) zachodzą bardziej logiczne odczyty, wyniki wydajności są następujące:

  • Instrukcje SELECT są porównywalne
  • Instrukcje UPDATE są szybsze dzięki indeksowi klastrowemu
  • Instrukcje DELETE są szybsze z indeksem klastrowym
  • Instrukcje INSERT są szybsze z indeksem klastrowym

Oczywiście mój test porównawczy był bardzo ograniczony w odniesieniu do określonego rodzaju tabeli i bardzo ograniczonego zestawu zapytań, ale myślę, że na podstawie tych informacji możemy już zacząć mówić, że praktycznie zawsze lepiej jest utworzyć indeks klastrowy na stole.

Aktualizacja w dniu 9 marca 2011 r . :

Jak widać z dodanych wyników, wnioski dotyczące ograniczonych testów nie były poprawne w każdym przypadku.

Ważony czas trwania

Wyniki wskazują teraz, że jedynymi instrukcjami, które korzystają z indeksu klastrowanego, są instrukcje aktualizacji. Pozostałe instrukcje są o około 30% wolniejsze w tabeli z indeksem klastrowym.

Niektóre dodatkowe wykresy, na których wykreśliłem ważony czas trwania zapytania dla sterty vs klastra. Kupa ważonego czasu trwania vs skupiona dla Select

Kupa ważonego czasu trwania vs skupiona dla dołączenia

Sterta ważonego czasu trwania vs skupiona w celu aktualizacji

Sterta ważonego czasu trwania vs klastrowana dla Usuń

Jak widać profil wydajności instrukcji wstawiania jest dość interesujący. Skoki są spowodowane kilkoma punktami danych, których wypełnienie zajmuje dużo więcej czasu. Kupa ważonego czasu trwania vs skupiona dla wstawiania

Koniec aktualizacji 9 marca 2011 r .

Filip De Vos
źródło
@Martin Spróbuję uruchomić to na serwerze z kilkoma tabelami z 500 milionami rekordów, kiedy znajdę trochę czasu w przyszłym tygodniu.
Filip De Vos,
Wątpię w prawdziwość tego testu. Niektóre części wymagają poważnej uwagi, takie jak wydajność INSERT, która twierdzi, że indeks klastrowany jest szybszy - w wersji CLUST było więcej odczytów, ale upływ czasu jest krótszy. Osobiście zignorowałbym upływający czas w ciągu 10s milisekund (zmienność czasowa) - oznacza to mniej niż licznik odczytów.
Sprawdź debatę Kimberly Tripp The Clustered Index Debata kontynuuje, gdzie wyjaśnia, dlaczego większość (jeśli nie wszystkie) operacje z tabelą klastrową są szybsze niż ze stertą - niektóre w przeciwieństwie do twoich wyników ...
marc_s
1
@Martin, @Richard, @marc_s. Pracuję teraz nad poważniejszym testem porównawczym. Mam nadzieję, że będę mógł później dodać wyniki.
Filip De Vos
1
@Filip - Wow! Zdecydowanie zasługujesz na nagrodę za ciężką pracę włożoną w tę odpowiedź. Chociaż, jak słusznie zauważyłeś, był to jeden punkt odniesienia dla konkretnego rodzaju stołu z bardzo ograniczonym zestawem zapytań, a przebieg na pewno będzie się różnić.
Martin Smith,
12

Jak Kimberly Tripp - Królowa Indeksowania - całkiem ładnie wyjaśnia w swoim blogu Trwa debata na temat indeksów klastrowanych ... posiadanie klucza klastrowania w tabeli bazy danych znacznie przyspiesza wszystkie operacje - nie tylko SELECT.

SELECT są generalnie wolniejsze na stosie w porównaniu do tabeli klastrowej, o ile wybierzesz dobry klucz klastrowania - coś w rodzaju INT IDENTITY. Jeśli użyjesz naprawdę bardzo złego klucza klastrowania, takiego jak identyfikator GUID lub klucz złożony z wieloma składnikami o zmiennej długości, wtedy, ale tylko wtedy, sterty mogą być szybsze. Ale w takim przypadku naprawdę musisz oczyścić projekt bazy danych ...

Ogólnie rzecz biorąc, nie sądzę, że na stosie jest sens - wybierz dobry, użyteczny klucz do grupowania i powinieneś skorzystać pod każdym względem.

marc_s
źródło
3
To nie jest odpowiedź. Martin jest dość solidny na SQL Server; pytanie miało na celu przetestowanie rzeczywistych zweryfikowanych wyników z testów wydajności, a nie więcej teorii.
Link do artykułu Kimberly Tripp skutecznie zakłada, że ​​obejmują wszystkie indeksy nieklastrowane. W takim przypadku nie byłoby żadnych wyszukiwań, a przewaga stosu w przeglądach byłaby zanegowana. Jednak to nie jest świat, w którym większość z nas żyje. W naszych przypadkach próba zaprojektowania wszystkich lub większości naszych nieklastrowanych indeksów do pokrycia tworzy własne problemy.
@ dbaguy52: jak myślisz, dlaczego Kim Tripp zakłada, że ​​obejmują wszystkie indeksy NC? Nie widzę żadnego pojęcia na ten temat w jej wpisie na blogu ..... proszę wyjaśnić bardziej szczegółowo, co sprawia, że ​​uważasz, że tak jest (lub takie jest jej założenie)
marc_s
7

Właśnie natknąłem się na ten artykuł od Joe Chang, który dotyczy tego pytania. Wkleiłem swoje wnioski poniżej.

Rozważmy tabelę, dla której indeksy mają głębokość 4, tak aby istniał poziom główny, 2 poziomy pośrednie i poziom liścia. Wyszukiwanie indeksu dla pojedynczego klucza indeksu (to znaczy bez wyszukiwania klucza) wygenerowałoby 4 logiczne operacje we / wy (LIO). Teraz zastanów się, czy wymagane jest wyszukiwanie klucza. Jeśli tabela ma indeks klastrowy również o głębokości 4, każde wyszukiwanie klucza generuje 4 LIO. Jeśli tabela była stertą, każde wyszukiwanie klucza generuje 1 LIO. W rzeczywistości wyszukiwanie klucza do sterty jest o 20-30% tańsze niż wyszukiwanie klucza do indeksu klastrowego, nie jest to wcale blisko wskaźnika LIO 4: 1.

Martin Smith
źródło
1
Ciekawą rzeczą do odnotowania jest to, że cytat Joe Changa określił przewagę wydajności wynoszącą 20-30% dla hałd na podstawie jego założeń, co jest prawie taką samą przewagą zidentyfikowaną w aktualizacji artykułu z 9 marca.