web-dev-qa-db-fra.com

SQL Select prend trop de temps à exécuter

Il s'agit d'une simple sélection dans une table temporaire, reliant à gauche une table existante sur sa clé primaire, avec deux sous-sélections utilisant le top 1 faisant référence à la table jointe.

Dans du code:

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn1 AND 
    TempTable.PKColumn2 = JoinedTable.PKColumn2)
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Ceci est une réplique exacte de ma requête.

Si je supprime les deux sous-sélections, cela fonctionne très bien et rapidement. Avec les deux sous-sélections, j'obtiens environ 100 enregistrements par seconde, ce qui est extrêmement lent pour cette requête car elle devrait renvoyer près d'un million d'enregistrements.

J'ai vérifié si chaque table a une clé primaire, elles le font toutes. Ils ont tous des index et des statistiques pour leurs colonnes importantes, comme ceux de ces clauses WHERE et ceux de la clause JOIN. La seule table sans clé primaire définie ni index est la table temporaire, mais ce n'est pas le problème non plus car ce n'est pas celle liée aux sous-sélections lentes, et comme je l'ai mentionné, sans sous-sélection, elle fonctionne très bien.

Sans ces TOP 1 il renvoie plus d'un résultat et déclenche une erreur.

De l'aide, quelqu'un?

MODIFIER :

Le plan d'exécution m'a donc dit qu'il me manquait un index. Je l'ai créé et recréé certains des autres index. Après un certain temps, le plan d'exécution les utilisait et la requête s'exécute désormais rapidement. Le seul problème est que je ne réussis pas à refaire cela sur un autre serveur, pour la même requête. Donc, ma solution sera d'indiquer quel index SQL Server utilisera.

9
Smur

Je pense que dans une requête d'un million d'enregistrements, vous devez éviter des choses comme OUTER JOINS. Je vous suggère d'utiliser UNION ALL Au lieu de LEFT JOIN. Tant que je pense que CROSS APPLY Est plus efficace que la sous-requête dans la clause select, je modifierai la requête écrite par Conard Frix, qui je pense est correcte.

maintenant: quand j'ai commencé à modifier votre requête, j'ai remarqué que vous aviez une clause WHERE disant: JoinedTable.WhereColumn IN (1, 3). dans ce cas, si le champ est nul, la condition devient fausse. alors pourquoi utilisez-vous LEFT JOIN pendant que vous filtrez les lignes de valeur nulle? remplacez simplement LEFT JOIN par INNER JOIN, je vous garantis que cela deviendra plus rapide.

sur INDEX:

veuillez noter que lorsque vous avez un index sur une table, dites

table1(a int, b nvarchar)

et votre index est:

nonclustered index ix1 on table1(a)

et vous voulez faire quelque chose comme ça:

select a,b from table1
where a < 10

dans votre index, vous n'avez pas inclus la colonne b alors que se passe-t-il?

si sql-server utilise votre index, il devra chercher dans l'index, appelé "Index Seek" puis se référer à la table principale pour obtenir la colonne b, appelée "Rechercher". Cette procédure peut prendre beaucoup plus de temps que l'analyse de la table elle-même: "Table Scan".

mais sur la base des statistiques dont dispose sql-server, dans de telles situations, il se peut qu'il n'utilise pas du tout votre index.

vérifiez donc tout d'abord le Execution Plan pour voir si l'index est utilisé.

si oui ou non les deux, modifiez votre index pour inclure toutes les colonnes que vous sélectionnez. dire comme:

nonclustered index ix1 on table1(a) include(b)

dans ce cas, la recherche ne sera pas nécessaire et votre requête s'exécutera beaucoup plus rapidement.

7
Maziar Taheri

C'est le sous-sélection dans votre sélection de colonne qui provoque le retour lent. Vous devriez essayer d'utiliser vos sous-sélections dans les jointures de gauche, ou utiliser une table dérivée comme je l'ai défini ci-dessous.

tilisation des jointures gauches pour deux instances de la troisième table

SELECT
  TempTable.Col1,
  TempTable.Col2,
  TempTable.Col3,
  JoinedTable.Col1,
  JoinedTable.Col2,
  ThirdTable.Col1 AS ThirdTableColumn1,
  ThirdTable2.Col1 AS ThirdTableColumn2
FROM #TempTable as TempTable
LEFT JOIN JoinedTable ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTable.PKColumn2)
LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

en utilisant une table dérivée

 SELECT 
      TempTable.Col1,
      TempTable.Col2,
      TempTable.Col3,
      DerivedTable.Col1,
      DerivedTable.Col2,
      DerivedTable.ThirdTableColumn1,
      DerivedTable.ThirdTableColumn2
 FROM #TempTable as TempTable
    LEFT JOIN (SELECT
                 JoinedTable.PKColumn2,
                 JoinedTable.Col1,
                 JoinedTable.Col2,
                 JoinedTable.WhereColumn,
                 ThirdTable.Col1 AS ThirdTableColumn1,
                 ThirdTable2.Col1 AS ThirdTableColumn2
               FROM JoinedTable
               LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
               LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn) 
        DerivedTable ON (TempTable.PKColumn1 = DerivedTable .PKColumn2 AND 
        TempTable.PKColumn2 = DerivedTable.PKColumn2)
    WHERE
        DerivedTable.WhereColumn IN  (1, 3)
6
John Hartsock

Essayez plutôt une application croisée

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    ThirdTableColumn1.col1,
    ThirdTableColumn2.col1

FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTablePKColumn2)

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Vous pouvez également utiliser les CTE et row_number ou une requête en ligne à l'aide de MIN

2
Conrad Frix

Déplacez les bits JOIN hors de la partie principale de la clause et placez-les en tant que sous-sélection. Le déplacer vers la section WHERE et JOIN vous garantit que vous n'avez pas à sélectionner TOP 1 encore et encore, ce qui, je crois, est la raison de sa lenteur. Si vous souhaitez vérifier cela, examinez le plan d'exécution.

2
Gregory A Beamer

Les références ThirdTable (sous-sélections dans votre exemple) nécessitent la même attention d'index que toute autre partie d'une requête.

Peu importe si vous utilisez des sous-sélections:

(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeColumn = JoinedTable.SomeColumn
) as ThirdTableColumn1,
(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
) as ThirdTableColumn2,

JOINT GAUCHE (tel que proposé par John Hartsock):

LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn

CROSS APPLY (comme proposé par Conrad Frix):

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2

Vous devez vous assurer que covering indexes sont définis pour ThirdTable.SomeColumn et ThirdTable.SomeOtherColumn et les index sont uniques. Cela signifie que vous devrez qualifier davantage les références ThirdTable pour éliminer la sélection de plusieurs lignes et améliorer les performances. Le choix de sub selects, LEFT JOIN, ou CROSS APPLY n'aura pas vraiment d'importance tant que vous n'aurez pas amélioré la sélectivité pour ThirdTable.SomeColumn et ThirdTable.SomeOtherColumn en incluant plus de colonnes pour garantir une sélectivité unique. D'ici là, je m'attends à ce que vos performances continuent de souffrir.

Le covering index le sujet est bien présenté par Maziar Taheri; sans répéter son travail, j'insiste sur la nécessité de prendre à cœur l'utilisation des index de recouvrement.

En bref: améliorer la sélectivité pour le ThirdTable.SomeColumn et ThirdTable.SomeOtherColumn requêtes (ou jointures) en ajoutant des colonnes dans la table associées pour assurer une correspondance de ligne unique. Si cela n'est pas possible, vous continuerez à souffrir de problèmes de performances car le moteur est occupé à tirer des rangées qui sont ensuite jetées. Cela a un impact sur vos E/S, votre processeur et, en fin de compte, sur le plan d'exécution.

2
Robert Miller