Performance des index non clusterisés sur les tas comparés aux index clusterisés

39

Ce livre blanc de 2007 compare les performances des instructions de sélection / insertion / suppression / mise à jour et de sélection de plage d'une table organisée en tant qu'index en cluster par rapport à celle d'une table organisée en segment de mémoire avec un index non en cluster sur les mêmes colonnes de clé que le CI. table.

En règle générale, l'option d'indexation en cluster s'est mieux comportée dans les tests car il n'y a qu'une seule structure à gérer et qu'il n'est pas nécessaire de rechercher des signets.

Un cas potentiellement intéressant non couvert par le document aurait été une comparaison entre un index non clusterisé sur un segment de mémoire et un index non clusterisé sur un index clusterisé. Dans ce cas, je m'attendais à ce que le segment de mémoire fonctionne même mieux, car une fois au niveau feuille de NCI, SQL Server a un RID à suivre directement au lieu de devoir traverser l'index en cluster.

Est-ce que quelqu'un a connaissance d'essais formels similaires qui ont été effectués dans ce domaine et, dans l'affirmative, quels ont été les résultats?

Martin Smith
la source

Réponses:

41

Pour vérifier votre demande, j'ai créé 2 tables suivant ce schéma:

  • 7,9 millions d’enregistrements représentant des informations sur le solde.
  • un champ d'identité comptant de 1 à 7,9 millions
  • un champ numérique regroupant les enregistrements dans environ 500 000 groupes.

La première table appelée heapa un index non clusterisé sur le champ group. La deuxième table appelée a clustobtenu un index clusterisé sur le champ séquentiel appelé keyet un index non clusterisé sur le champgroup

Les tests ont été exécutés sur un processeur I5 M540 avec 2 cœurs hyperthreaded, 4 Go de mémoire et fenêtres 7 bits 64 bits.

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)  

Mise à jour le 9 mars 2011 : j'ai effectué un deuxième test d'évaluation plus complet en exécutant le code .net suivant et en enregistrant la durée, le processeur, les lectures, les écritures et les montants en colonnes dans Sql Server Profiler. (Le CommandText utilisé sera mentionné dans les résultats.)

REMARQUE: La CPU et la durée sont exprimés en millisecondes.

  • 1000 requêtes
  • zéro requête CPU est éliminée des résultats
  • 0 lignes affectées sont éliminées des résultats
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));
                        }
                    }
                }
            }

Fin de la mise à jour le 9 mars 2011 .

SELECT performance

Pour vérifier les numéros de performance, j'ai effectué les requêtes suivantes une fois sur la table des tas et une fois sur la table 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

Les résultats de cette référence sont les suivants 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

Mise à jour du 9 mars 2011 : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 lignes ont> 0 CPU et affectent plus de 0 lignes
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

Fin de la mise à jour le 9 mars 2011 .


pour le tableau clustles résultats sont:

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

Mise à jour du 9 mars 2011 : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 lignes ont> 0 CPU et affectent plus de 0 lignes
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

Fin de la mise à jour le 9 mars 2011 .


Performances SELECT SELECT WITH JOIN

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


Les résultats de cette référence sont les suivants heap:

873 lignes ont> 0 CPU et affectent plus de 0 lignes

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

Les résultats de cette référence sont les suivants clust:

865 lignes ont> 0 CPU et affectent plus de 0 lignes

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 performance

Le deuxième lot de requêtes sont des instructions de mise à jour:

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

les résultats de ce repère pour 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

Mise à jour du 9 mars 2011 : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 lignes ont> 0 CPU et affectent plus de 0 lignes
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

Fin de la mise à jour le 9 mars 2011 .


les résultats de ce repère pour 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

Mise à jour du 9 mars 2011 : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853 lignes ont> 0 processeur et affectent plus de 0 lignes
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

Fin de la mise à jour le 9 mars 2011 .


SUPPRIMER les repères

le troisième lot de requêtes que j'ai exécuté sont des instructions de suppression

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

Le résultat de cette référence pour le 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

Mise à jour du 9 mars 2011 : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 lignes ont> 0 processeur et affectent plus de 0 lignes
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

Fin de la mise à jour le 9 mars 2011 .


le résultat de cette référence pour le 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

Mise à jour du 9 mars 2011 :

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

  • 751 lignes ont> 0 processeur et affectent plus de 0 lignes
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

Fin de la mise à jour le 9 mars 2011 .


INSÉRER des repères

La dernière partie de l’indice de référence est l’exécution des instructions insert.

insérer dans tas / clust (...) les valeurs (...), (...), (...), (...), (...), (...)


Le résultat de cette référence pour le heap:

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

Mise à jour du 9 mars 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 instructions ont> 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

Fin de la mise à jour le 9 mars 2011 .


Le résultat de cette référence pour le clust:

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

Mise à jour du 9 mars 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 instructions ont> 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

Fin de la mise à jour le 9 mars 2011 .


Conclusions

Bien qu'il y ait davantage de lectures logiques en cours lors de l'accès à la table avec l'index clusterisé et non clusterisé (tout en utilisant l'index non clusterisé), les résultats de performance sont les suivants:

  • Les instructions SELECT sont comparables
  • Les instructions UPDATE sont plus rapides avec un index clusterisé en place
  • Les instructions DELETE sont plus rapides avec un index clusterisé en place
  • Les instructions INSERT sont plus rapides avec un index clusterisé en place

Bien sûr, mon repère était très limité sur un type de table spécifique et avec un ensemble de requêtes très limité, mais je pense que sur la base de ces informations, nous pouvons déjà commencer à dire qu'il est pratiquement toujours préférable de créer un index clusterisé sur votre table.

Mise à jour du 9 mars 2011 :

Comme nous pouvons le constater grâce aux résultats ajoutés, les conclusions sur les tests limités n’étaient pas correctes dans tous les cas.

Durée pondérée

Les résultats indiquent maintenant que les seules instructions bénéficiant de l'index clusterisé sont les instructions de mise à jour. Les autres instructions sont environ 30% plus lentes sur la table avec index clusterisé.

Quelques graphiques supplémentaires où j'ai tracé la durée pondérée par requête pour tas vs clust. Durée pondérée tas vs en cluster pour Select

Durée pondérée tas vs clusterisé pour rejoindre

Durée pondérée du tas vs en cluster pour mise à jour

Durée pondérée du tas vs en cluster pour la suppression

Comme vous pouvez le constater, le profil de performance des instructions d’insertion est très intéressant. Les pointes sont causées par quelques points de données qui prennent beaucoup plus de temps à compléter. Durée pondérée du tas vs en cluster pour l'insertion

Fin de la mise à jour le 9 mars 2011 .

Filip De Vos
la source
@Martin J'essaierai de l'exécuter sur un serveur comportant quelques tables contenant 500 millions d'enregistrements lorsque je retrouverai du temps la semaine prochaine.
Filip De Vos
Je doute de la véracité de ce test. Certaines parties nécessitent une attention particulière, telles que les performances INSERT affirmant que l'index clusterisé est plus rapide: la version CLUST contenait plus de lectures, mais le temps écoulé était inférieur. Personnellement, j'aurais ignoré que le temps écoulé se situe dans les 10 secondes de millisecondes (variabilité temporelle) - cela signifie moins que le nombre de lectures.
Découvrez le débat sur les grappes en grappes de Kimberly Tripp, où elle explique pourquoi la plupart (sinon toutes) les opérations avec une table en grappes sont plus rapides qu'avec un tas - certaines sont contraires à vos résultats ...
indexées de marc_s
1
@Martin, @Richard, @marc_s. Je travaille sur un repère plus sérieux en ce moment. J'espère pouvoir ajouter les résultats plus tard aujourd'hui.
Filip De Vos
1
@ Filip - Wow! Vous méritez assurément la prime pour tout le dur travail que vous avez consacré à cette réponse. Comme vous l'avez fait remarquer à juste titre, il s'agissait d'un point de repère sur un type de table spécifique avec un nombre très limité de requêtes et le kilométrage variera sans aucun doute.
Martin Smith
12

Comme l'explique bien Kimberly Tripp - la reine de l'indexation - dans son billet de blog The Clustered Index Debate ... , le fait de disposer d'une clé de clustering sur une table de base de données accélère sensiblement toutes les opérations, et pas seulement SELECT.

Les commandes SELECT sont généralement plus lentes sur un segment comparé à une table en cluster, à condition que vous sélectionniez une bonne clé de mise en cluster - quelque chose comme un INT IDENTITY. Si vous utilisez une clé de clusterage vraiment mauvaise, comme un GUID ou une clé composée avec beaucoup de composants de longueur variable, un segment de mémoire peut être plus rapide, mais seulement à ce moment-là. Mais dans ce cas, vous devez vraiment nettoyer la conception de votre base de données en premier lieu ...

Donc, en général, je ne pense pas qu’il soit inutile de choisir une bonne clé de regroupement et vous devriez en tirer profit à tous les égards.

marc_s
la source
3
Ceci est une non-réponse. Martin est plutôt solide sur SQL Server; la question visait à obtenir des résultats vérifiés dans le monde réel à partir de tests de performance, et non plus de théorie.
L'article lié de Kimberly Tripp suppose effectivement que tous les index non clusterisés couvrent. Si tel est le cas, il n'y aura pas de recherche et l'avantage du segment de mémoire serait annulé. Ce n'est pas un monde dans lequel la plupart d'entre nous vivons. Dans nos cas, essayer de concevoir la totalité ou la plupart de nos index non clusterisés de manière à couvrir crée ses propres problèmes.
@ dbaguy52: pourquoi, selon vous, Kim Tripp suppose-t-il que tous les indices NC couvrent? Je ne vois aucune notion de cela dans son billet de blog ..... veuillez expliquer plus en détail ce qui vous fait croire que c'est le cas (ou c'est son hypothèse)
marc_s
7

Je viens de tomber sur cet article de Joe Chang qui répond à cette question. Collé ses conclusions ci-dessous.

Considérons une table pour laquelle les index ont une profondeur de 4, de sorte qu'il y ait un niveau racine, 2 niveaux intermédiaires et le niveau feuille. La recherche d'index pour une clé d'index unique (c'est-à-dire, aucune recherche de clé) générerait 4 E / S logiques (LIO). Maintenant, considérez si une recherche de clé est requise. Si la table a également un index clusterisé de profondeur 4, chaque recherche de clé génère 4 LIO. Si la table était un segment de mémoire, chaque recherche de clé génère 1 LIO. En réalité, la recherche de clé dans un segment de mémoire est environ 20 à 30% moins chère qu'une recherche de clé dans un index clusterisé, pas très près du ratio 4: 1 LIO.

Martin Smith
la source
1
Il est intéressant de noter que la citation de Joe Chang a identifié un avantage d'efficacité de 20 à 30% pour les tas, basé sur ses hypothèses, ce qui correspond à peu près au même avantage identifié dans la mise à jour de l'article du 9 mars.