Index ne rend pas l'exécution plus rapide et, dans certains cas, ralentit la requête. Pourquoi est-ce?

34

J'essayais avec des index pour accélérer les choses, mais dans le cas d'une jointure, l'index n'améliore pas le temps d'exécution de la requête et, dans certains cas, il ralentit le processus.

La requête pour créer une table de test et la remplir avec des données est la suivante:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

Maintenant, la requête 1, qui est améliorée (légèrement mais l'amélioration est constante) est la suivante:

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

Statistiques et plan d'exécution sans index (dans ce cas, la table utilise l'index clusterisé par défaut):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

entrez la description de l'image ici

Maintenant, avec l'index activé:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

entrez la description de l'image ici

Maintenant, la requête qui ralentit en raison de l'index (la requête n'a pas de sens puisqu'elle est créée pour les tests uniquement):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

Avec l'index clusterisé activé:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

entrez la description de l'image ici

Maintenant avec Index désactivé:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

entrez la description de l'image ici

Les questions sont:

  1. Bien que l'index soit suggéré par SQL Server, pourquoi ralentit-il les choses d'une différence significative?
  2. Quelle est la jointure de boucle imbriquée qui prend le plus de temps et comment améliorer son temps d'exécution?
  3. Y a-t-il quelque chose que je fais mal ou que j'ai manqué?
  4. Avec l'index par défaut (sur la clé primaire uniquement), pourquoi prend-il moins de temps, et avec l'index non en cluster présent, pour chaque ligne de la table de jointure, la ligne de la table jointe doit être trouvée plus rapidement, car join est dans la colonne Nom sur laquelle l'index a été créé. Cela se reflète dans le plan d'exécution de la requête et le coût de la recherche dans l'Index est inférieur lorsque IndexA est actif, mais pourquoi rester plus lent? De plus, que se passe-t-il dans la jointure externe gauche de la boucle imbriquée qui provoque le ralentissement?

Utiliser SQL Server 2012

SpeedBirdNine
la source

Réponses:

23

Bien que l'index soit suggéré par SQL Server, pourquoi ralentit-il les choses d'une différence significative?

Les suggestions d'index sont faites par l'optimiseur de requêtes. S'il trouve une sélection logique dans une table qui n'est pas bien servie par un index existant, il peut ajouter une suggestion "index manquant" à sa sortie. Ces suggestions sont opportunistes. ils ne sont pas basés sur une analyse complète de la requête et ne tiennent pas compte de considérations plus générales. Au mieux, ils indiquent qu'une indexation plus utile pourrait être possible, et un administrateur de base de données qualifié devrait y jeter un coup d'œil.

L'autre chose à dire sur les suggestions d'index manquantes est qu'elles sont basées sur le modèle de calcul des coûts de l'optimiseur et que l'optimiseur estime de combien l'indice suggéré pourrait réduire le coût estimé de la requête. Les mots clés ici sont "modèle" et "estimations". L'optimiseur de requêtes connaît peu de choses sur votre configuration matérielle ou sur les autres options de configuration système. Son modèle repose en grande partie sur des nombres fixes qui produisent des résultats de plan raisonnables pour la plupart des utilisateurs de la plupart des systèmes. Mis à part les problèmes avec les chiffres de coût exact utilisés, les résultats sont toujours des estimations - et les estimations peuvent être fausses.

Quelle est la jointure de boucle imbriquée qui prend le plus de temps et comment améliorer son temps d'exécution?

Il reste peu à faire pour améliorer les performances de l'opération de jointure croisée elle-même. Les boucles imbriquées sont la seule implémentation physique possible pour une jointure croisée. La bobine de table sur le côté intérieur de la jointure est une optimisation pour éviter de réanalyser le côté intérieur pour chaque ligne extérieure. L'optimisation des performances dépend de divers facteurs, mais dans mes tests, la requête est meilleure sans elle. Encore une fois, ceci est une conséquence de l’utilisation d’un modèle de coût: mon processeur et mon système de mémoire ont probablement des caractéristiques de performances différentes de celles du vôtre. Il n'y a pas d'indicateur de requête spécifique pour éviter le spool de table, mais il existe un indicateur de trace non documenté (8690) que vous pouvez utiliser pour tester les performances d'exécution avec et sans spool. Si c’était un vrai problème de système de production, le plan sans le spool peut être forcé à l'aide d'un guide de plan basé sur le plan produit avec TF 8690 activé. L'utilisation d'indicateurs de trace non documentés en production n'est pas recommandée, car l'installation devient techniquement non prise en charge et les indicateurs de trace peuvent avoir des effets secondaires indésirables.

Y a-t-il quelque chose que je fais mal ou que j'ai manqué?

La principale chose qui vous manque est que, bien que le plan utilisant l'indice non clusterisé ait un coût estimé inférieur selon le modèle de l'optimiseur, il pose un problème de temps d'exécution important. Si vous examinez la répartition des lignes entre les threads du plan à l'aide de l'index clusterisé, vous constaterez probablement une distribution raisonnablement bonne:

Plan de numérisation

Dans le plan utilisant la recherche d'index non clusterisée, le travail est entièrement exécuté par un seul thread:

Plan de recherche

Ceci est une conséquence de la façon dont le travail est réparti entre les threads par des opérations d'analyse / recherche en parallèle. Il n’est pas toujours vrai qu’un balayage parallèle distribue mieux le travail qu’une recherche d’index - mais il le fait dans ce cas. Des plans plus complexes peuvent inclure la répartition des échanges pour redistribuer le travail entre les threads. Ce plan ne comporte pas de tels échanges. Par conséquent, une fois que les lignes sont attribuées à un fil, tous les travaux associés sont effectués sur ce même fil. Si vous examinez la répartition du travail pour les autres opérateurs du plan d'exécution, vous verrez que tout le travail est effectué par le même thread, comme indiqué pour la recherche d'index.

Il n'y a pas d'indication de requête pour affecter la distribution des lignes entre les threads, l'important est d'être conscient de la possibilité et de pouvoir lire suffisamment de détails dans le plan d'exécution pour déterminer quand il pose problème.

Avec l'index par défaut (sur la clé primaire uniquement), pourquoi prend-il moins de temps, et avec l'index non en cluster présent, pour chaque ligne de la table de jointure, la ligne de la table jointe doit être trouvée plus rapidement, car join est dans la colonne Nom sur laquelle l'index a été créé. Cela se reflète dans le plan d'exécution de la requête et le coût de la recherche dans l'Index est inférieur lorsque IndexA est actif, mais pourquoi rester plus lent? De plus, que se passe-t-il dans la jointure externe gauche de la boucle imbriquée qui provoque le ralentissement?

Il devrait maintenant être clair que le plan d’indexation non clusterisé est potentiellement plus efficace, comme on peut s’y attendre; c'est simplement une mauvaise répartition du travail entre les threads au moment de l'exécution qui explique le problème de performances.

Pour compléter l'exemple et illustrer certaines des choses que j'ai mentionnées, un moyen d'obtenir une meilleure répartition du travail consiste à utiliser une table temporaire pour piloter l'exécution en parallèle:

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

Cela se traduit par un plan qui utilise l'index plus efficace, ne comporte pas de spool de table et distribue bien le travail entre les threads:

Plan optimal

Sur mon système, ce plan est exécuté beaucoup plus rapidement que la version d'analyse en cluster en cluster.

Si vous souhaitez en savoir plus sur les éléments internes de l'exécution de requêtes parallèles, vous pouvez regarder mon enregistrement de session PASS Summit 2013 .

Paul White dit GoFundMonica
la source
0

Ce n’est pas vraiment une question d’index, c’est plutôt une requête mal écrite. Vous avez seulement 100 valeurs uniques de nom, ce qui laisse un nombre unique de 5000 par nom.

Ainsi, pour chaque ligne du tableau 1, vous rejoignez 5 000 du tableau 2. Pouvez-vous dire 25020004 lignes?

Essayez ceci, notez qu’il n’ya qu’un seul index, celui que vous avez énuméré.

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

Et le temps:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

entrez la description de l'image ici

Vous ne pouvez pas blâmer les index SQL pour des requêtes mal formées

Adrian Sullivan
la source
1
Merci pour la réponse, et oui la requête peut être améliorée, mais la logique de ma question était que, avec l'index par défaut (sur la clé primaire uniquement), pourquoi prend-il moins de temps, et avec l'index non cluster présent, pour chaque ligne de la table jointe, la ligne de la table jointe doit être trouvée plus rapidement, ce qui est reflété dans le plan d'exécution de la requête et le coût de la recherche dans Index est inférieur lorsque IndexA est actif, mais pourquoi encore plus lent? De plus, que se passe-t-il dans la jointure externe gauche de la boucle imbriquée qui provoque le ralentissement? J'ai édité la question pour ajouter ce commentaire, pour la rendre plus claire.