web-dev-qa-db-fra.com

L'index ne rend pas l'exécution plus rapide et, dans certains cas, ralentit la requête. Pourquoi en est-il ainsi?

J'expérimentais avec des index pour accélérer les choses, mais en cas de jointure, l'index n'améliore pas le temps d'exécution des requêtes et dans certains cas, il ralentit les choses.

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

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 (seulement légèrement mais l'amélioration est cohérente) est:

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 cluster 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.

enter image description here

Maintenant, avec 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.

enter image description here

Maintenant, la requête qui ralentit en raison de l'index (la requête n'a pas de sens car 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 un index cluster 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.

enter image description here

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.

enter image description here

Les questions sont:

  1. Même si l'index est suggéré par SQL Server, pourquoi ralentit-il les choses par une différence significative?
  2. Quelle est la jointure de boucle imbriquée qui prend la plupart du 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 cela prend-il moins de temps, et avec l'index non cluster présent, pour chaque ligne de la table de jointure, la ligne de la table jointe devrait être trouvée plus rapidement, car la jointure se trouve sur la colonne Nom sur laquelle l'index a été créé. Cela se reflète dans le plan d'exécution des requêtes et le coût de la recherche d'index est inférieur lorsque IndexA est actif, mais pourquoi toujours plus lent? De plus, qu'est-ce qui se trouve dans la jointure externe gauche de la boucle imbriquée qui provoque le ralentissement?

Utilisation de SQL Server 2012

35
SpeedBirdNine

Même si l'index est suggéré par SQL Server, pourquoi ralentit-il les choses par une différence significative?

Les suggestions d'index sont faites par l'optimiseur de requête. S'il rencontre une sélection logique d'une table qui n'est pas bien desservie par un index existant, il peut ajouter une suggestion "d'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 larges. Au mieux, ils indiquent qu'une indexation plus utile peut être possible, et un administrateur de base de données qualifié devrait y jeter un œil.

L'autre chose à dire sur les suggestions d'index manquantes est qu'elles sont basées sur le modèle de coût de l'optimiseur et les estimations de l'optimiseur de combien l'index suggéré pourrait réduire l'estimation coût de la requête. Les mots clés ici sont "modèle" et "estimations". L'optimiseur de requêtes en sait peu sur votre configuration matérielle ou d'autres options de configuration système - son modèle est largement basé sur des nombres fixes qui produisent des résultats de plan raisonnables pour la plupart des utilisateurs sur la plupart des systèmes la plupart du temps. Mis à part les problèmes avec les nombres de coûts exacts 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 la plupart du temps et comment améliorer son temps d'exécution?

Il n'y a pas grand-chose à 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 externe. Que ce soit une optimisation des performances utile dépend de divers facteurs, mais dans mes tests, la requête est mieux sans elle. Encore une fois, c'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 performance différentes des vôtres. Il n'existe aucune indication de requête spécifique pour éviter le spouleur 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 le spouleur. S'il s'agissait d'un véritable problème de système de production, le plan sans bobine pourrait ê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 conseillée car l'installation n'est plus prise en charge techniquement 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'index non cluster ait un coût estimé inférieur selon le modèle de l'optimiseur, il présente un problème de temps d'exécution important. Si vous regardez la distribution des lignes sur les threads du plan à l'aide de l'index clusterisé, vous verrez probablement une distribution assez bonne:

Scan plan

Dans le plan utilisant la recherche d'index non clusterisé, le travail finit par être entièrement exécuté par un thread:

Seek plan

Ceci est une conséquence de la façon dont le travail est réparti entre les threads par des opérations de recherche/balayage parallèles. Ce n'est pas toujours le cas qu'un balayage parallèle distribuera mieux le travail qu'une recherche d'index - mais c'est le cas dans ce cas. Des plans plus complexes pourraient inclure la répartition des échanges pour redistribuer le travail entre les threads. Ce plan n'a pas de tels échanges, donc une fois que les lignes sont affectées à un thread, tout le travail associé est effectué sur ce même thread. Si vous regardez la répartition du travail pour les autres opérateurs dans le 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'indications 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 cela prend-il moins de temps, et avec l'index non cluster présent, pour chaque ligne de la table de jointure, la ligne de la table jointe devrait être trouvée plus rapidement , car la jointure se trouve dans la colonne Nom sur laquelle l'index a été créé. Cela se reflète dans le plan d'exécution des requêtes et le coût de la recherche d'index est inférieur lorsque IndexA est actif, mais pourquoi toujours plus lent? De plus, qu'est-ce qui se trouve dans la jointure externe gauche de la boucle imbriquée qui provoque le ralentissement?

Il devrait maintenant être clair que le plan d'index non cluster est potentiellement plus efficace, comme vous vous en doutez; c'est juste une mauvaise répartition du travail sur 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, une façon d'obtenir une meilleure distribution du travail consiste à utiliser une table temporaire pour piloter l'exécution 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;

Il en résulte un plan qui utilise les recherches d'index les plus efficaces, ne comporte pas de spouleur de table et répartit bien le travail entre les threads:

Optimal plan

Sur mon système, ce plan s'exécute beaucoup plus rapidement que la version Clustered Index Scan.

Si vous souhaitez en savoir plus sur les fonctions internes de l'exécution de requêtes parallèles, vous aimerez peut-être regarder mon enregistrement de session PASS Summit 201 .

24
Paul White 9

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 compte unique de 5000 par nom.

Donc, pour chaque ligne du tableau 1, vous joignez 5000 du tableau 2. Pouvez-vous dire 25020004 lignes.

Essayez ceci, notez qu'il s'agit d'un seul index, celui que vous avez indiqué.

    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.

enter image description here

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

0
Adrian Sullivan