Leistung von nicht gruppierten Indizes auf Heaps im Vergleich zu gruppierten Indizes

39

In diesem Whitepaper aus dem Jahr 2007 wird die Leistung für einzelne Select / Insert / Delete / Update- und Range-Select-Anweisungen in einer Tabelle, die als Clustered-Index organisiert ist, mit der Leistung in einer Tabelle verglichen, die als Heap mit einem nicht-Clustered-Index in denselben Schlüsselspalten wie das CI organisiert ist Tabelle.

Im Allgemeinen schnitt die Option für den gruppierten Index in den Tests besser ab, da nur eine Struktur verwaltet werden muss und keine Lesezeichensuche erforderlich ist.

Ein potenziell interessanter Fall, der in diesem Artikel nicht behandelt wurde, wäre ein Vergleich zwischen einem nicht gruppierten Index auf einem Heap und einem nicht gruppierten Index auf einem gruppierten Index gewesen. In diesem Fall hätte ich erwartet, dass der Heap möglicherweise sogar eine bessere Leistung erbringt, da SQL Server auf NCI-Blattebene eine RID hat, der direkt gefolgt werden muss, anstatt den Clustered-Index zu durchlaufen.

Hat jemand Kenntnis von ähnlichen formalen Tests, die in diesem Bereich durchgeführt wurden, und wenn ja, welche Ergebnisse wurden erzielt?

Martin Smith
quelle

Antworten:

41

Um Ihre Anfrage zu überprüfen, habe ich 2 Tabellen nach diesem Schema erstellt:

  • 7,9 Millionen Datensätze mit Informationen zum Kontostand.
  • ein Identitätsfeld von 1 bis 7,9 Millionen
  • Ein Zahlenfeld, das die Datensätze in ca. 500.000 Gruppen gruppiert.

Die erste aufgerufene Tabelle heaphat einen nicht gruppierten Index für das Feld group. Die zweite aufgerufene Tabelle clusthat einen Clustered-Index für das aufgerufene sequentielle Feld keyund einen Nonclustered-Index für das Feldgroup

Die Tests wurden auf einem I5 M540-Prozessor mit 2 Hyperthread-Kernen, 4 GB Speicher und 64-Bit-Fenstern 7 durchgeführt.

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)  

Update am 9. März 2011 : Ich habe einen zweiten umfassenderen Benchmark durchgeführt, indem ich den folgenden .net-Code und die Protokollierungsdauer, die CPU, die Lese-, Schreib- und Zeilenanzahl in SQL Server Profiler ausgeführt habe. (Der verwendete CommandText wird in den Ergebnissen erwähnt.)

HINWEIS: CPU und Dauer werden in Millisekunden angegeben

  • 1000 Abfragen
  • CPU-Anfragen von Null werden aus den Ergebnissen eliminiert
  • 0 betroffene Zeilen werden aus den Ergebnissen entfernt
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));
                        }
                    }
                }
            }

Ende der Aktualisierung am 9. März 2011 .

SELECT Leistung

Um die Performance-Zahlen zu überprüfen, habe ich die folgenden Abfragen einmal in der Heap-Tabelle und einmal in der Clust-Tabelle durchgeführt:

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

Die Ergebnisse dieses Benchmarks beziehen sich auf 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

Update am 9. März 2011 : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen
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

Ende der Aktualisierung am 9. März 2011 .


Für die Tabelle sind clustdie Ergebnisse:

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

Update am 9. März 2011 : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen
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

Ende der Aktualisierung am 9. März 2011 .


SELECT WITH JOIN-Leistung

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


Die Ergebnisse dieses Benchmarks beziehen sich auf heap:

873 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen

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

Die Ergebnisse dieses Benchmarks beziehen sich auf clust:

865 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen

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

UPDATE-Leistung

Die zweite Gruppe von Abfragen sind Aktualisierungsanweisungen:

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

Die Ergebnisse dieses Benchmarks für 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

Update am 9. März 2011 : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen
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

Ende der Aktualisierung am 9. März 2011 .


Die Ergebnisse dieses Benchmarks für 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

Update am 9. März 2011 : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen
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

Ende der Aktualisierung am 9. März 2011 .


LÖSCHEN Benchmarks

Die dritte Gruppe von Abfragen, die ich ausgeführt habe, sind delete-Anweisungen

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

Das Ergebnis dieses Benchmarks für die 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

Update am 9. März 2011 : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen
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

Ende der Aktualisierung am 9. März 2011 .


das Ergebnis dieser Benchmark für die 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

Update am 9. März 2011 :

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

  • 751 Zeilen haben> 0 CPU und betreffen mehr als 0 Zeilen
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

Ende der Aktualisierung am 9. März 2011 .


INSERT-Benchmarks

Der letzte Teil des Benchmarks ist die Ausführung von Insert-Anweisungen.

In Heap / Clust einfügen (...) Werte (...), (...), (...), (...), (...), (...)


Das Ergebnis dieses Benchmarks für die heap:

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

Update am 9. März 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 Anweisungen haben> 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

Ende der Aktualisierung am 9. März 2011 .


Das Ergebnis dieses Benchmarks für die clust:

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

Update am 9. März 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 Anweisungen haben> 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

Ende der Aktualisierung am 9. März 2011 .


Schlussfolgerungen

Obwohl beim Zugriff auf die Tabelle mit dem gruppierten und dem nicht gruppierten Index mehr logische Lesevorgänge stattfinden (während der nicht gruppierte Index verwendet wird), sind die Leistungsergebnisse:

  • SELECT-Anweisungen sind vergleichbar
  • UPDATE-Anweisungen sind schneller, wenn ein Clustered-Index vorhanden ist
  • DELETE-Anweisungen sind schneller, wenn ein Clustered-Index vorhanden ist
  • INSERT-Anweisungen sind schneller, wenn ein Clustered-Index vorhanden ist

Natürlich war mein Benchmark für eine bestimmte Art von Tabelle und mit einer sehr begrenzten Anzahl von Abfragen sehr begrenzt, aber ich denke, dass wir aufgrund dieser Informationen bereits sagen können, dass es praktisch immer besser ist, einen Clustered-Index für Ihre Tabelle zu erstellen.

Update am 9. März 2011 :

Wie wir aus den hinzugefügten Ergebnissen ersehen können, waren die Schlussfolgerungen zu den begrenzten Tests nicht in jedem Fall korrekt.

Gewichtete Duration

Die Ergebnisse zeigen nun, dass die einzigen Anweisungen, die vom Clustered-Index profitieren, die Update-Anweisungen sind. Die anderen Aussagen sind in der Tabelle mit gruppiertem Index um 30% langsamer.

Einige zusätzliche Diagramme, in denen ich die gewichtete Dauer pro Abfrage für Heap vs. Clust aufgezeichnet habe. Weighted Duration Heap vs Clustered für Select

Weighted Duration Heap vs Clustered für Join

Weighted Duration Heap vs Clustered für Update

Weighted Duration Heap vs Clustered für Löschen

Wie Sie sehen, ist das Leistungsprofil für die insert-Anweisungen sehr interessant. Die Spitzen werden durch einige Datenpunkte verursacht, deren Fertigstellung viel länger dauert. Weighted Duration Heap vs Clustered für Einfügen

Ende der Aktualisierung am 9. März 2011 .

Filip De Vos
quelle
@Martin Ich werde versuchen, dies auf einem Server mit ein paar Tabellen mit 500 Millionen Datensätzen auszuführen, wenn ich nächste Woche etwas Zeit finde.
Filip De Vos
Ich bezweifle die Richtigkeit dieses Tests. Einige Teile erfordern ernsthafte Aufmerksamkeit, z. B. die INSERT-Leistung, die angibt, dass der Clustered-Index schneller ist. In der CLUST-Version wurden mehr Lesevorgänge durchgeführt, die verstrichene Zeit ist jedoch kürzer. Ich persönlich hätte die abgelaufene Zeit ignoriert, die innerhalb von 10 Sekunden von Millisekunden liegt (zeitliche Variabilität) - dies bedeutet weniger als die gelesene Anzahl.
Schauen Sie sich Kimberly Tripps The Clustered Index Debate an, in der sie erklärt, warum die meisten (wenn nicht alle) Operationen mit einer gruppierten Tabelle schneller sind als mit einem Haufen - einige widersprechen Ihren Ergebnissen ...
marc_s
1
@Martin, @Richard, @marc_s. Ich arbeite gerade an einem ernsteren Benchmark. Ich hoffe, dass ich die Ergebnisse später hinzufügen kann.
Filip De Vos
1
@Filip - Wow! Sie verdienen auf jeden Fall das Kopfgeld für all die harte Arbeit, die Sie in diese Antwort gesteckt haben. Wie Sie zu Recht betonen, war dies ein Benchmark für eine bestimmte Art von Tabelle mit einer sehr begrenzten Anzahl von Abfragen, und die Anzahl der Meilen wird zweifellos variieren.
Martin Smith
12

Wie Kimberly Tripp - die Königin der Indizierung - in ihrem Blogbeitrag The Clustered Index Debate (Die Debatte um den Clustered Index) recht gut erklärt, beschleunigt das Vorhandensein eines Clustering-Schlüssels in einer Datenbanktabelle alle Vorgänge - nicht nur SELECT.

SELECT ist auf einem Heap im Vergleich zu einer gruppierten Tabelle im Allgemeinen langsamer, solange Sie einen guten Clustering - Schlüssel auswählen - so etwas wie eine INT IDENTITY. Wenn Sie einen wirklich sehr, sehr schlechten Clustering-Schlüssel wie eine GUID oder einen zusammengesetzten Schlüssel mit vielen Komponenten variabler Länge verwenden, ist ein Heap möglicherweise schneller. Aber in diesem Fall müssen Sie Ihr Datenbankdesign unbedingt bereinigen ...

Im Allgemeinen glaube ich nicht, dass ein Haufen Sinn macht - wählen Sie einen guten, nützlichen Clustering-Schlüssel und Sie sollten in jeder Hinsicht davon profitieren.

marc_s
quelle
3
Dies ist eine nicht beantwortete Frage. Martin ist ziemlich solide in SQL Server; Die Frage sollte die Ergebnisse von Leistungstests in der Praxis testen lassen, nicht mehr die Theorie.
Der verlinkte Kimberly Tripp-Artikel geht davon aus, dass alle nicht gruppierten Indizes abgedeckt sind. Wenn dies der Fall wäre, gäbe es keine Suchvorgänge, und der Vorteil des Heaps bei Suchvorgängen würde negiert. Das ist jedoch keine Welt, in der die meisten von uns leben. In unseren Fällen führt der Versuch, alle oder die meisten unserer nicht gruppierten Indizes so zu gestalten, dass sie abgedeckt werden, zu eigenen Problemen.
@ dbaguy52: warum glaubst du, geht Kim Tripp davon aus, dass alle NC-Indizes abgedeckt sind? Ich sehe keine Ahnung davon in ihrem Blog-Post ..... bitte erkläre genauer,
warum
7

Ich bin gerade auf diesen Artikel von Joe Chang gestoßen, der diese Frage anspricht. Fügte seine Schlussfolgerungen unten ein.

Stellen Sie sich eine Tabelle vor, für die die Indizes die Tiefe 4 haben, sodass es eine Wurzelebene, 2 Zwischenebenen und die Blattebene gibt. Die Indexsuche nach einem einzelnen Indexschlüssel (dh keine Schlüsselsuche) würde 4 logische E / A (LIO) generieren. Überlegen Sie nun, ob eine Schlüsselsuche erforderlich ist. Wenn die Tabelle einen Clustered-Index mit der Tiefe 4 hat, generiert jede Schlüsselsuche 4 LIO. Wenn die Tabelle ein Heap war, generiert jede Schlüsselsuche 1 LIO. Tatsächlich ist die Schlüsselsuche auf einem Heap-Speicher etwa 20 bis 30% günstiger als die Schlüsselsuche auf einem Clustered-Index, und dies bei weitem nicht in der Nähe des 4: 1-LIO-Verhältnisses.

Martin Smith
quelle
1
Interessant zu bemerken ist, dass das Zitat von Joe Chang einen Effizienzvorteil von 20-30% für Haufen ausweist, der auf seinen Annahmen basiert und im Wesentlichen dem im Update vom 9. März des Artikels identifizierten Vorteil entspricht.