SQL Select prend trop de temps à exécuter

9

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 ceux- TOP 1ci, il renvoie plus d'un résultat et génère 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 à recommencer sur un autre serveur, pour la même requête. Donc, ma solution sera d'indiquer quel index SQL Server utilisera.

Smur
la source
Wow, c'est impressionnant. Mais pouvez-vous diviser cela en plusieurs déclarations distinctes? Et qu'en est-il des procédures stockées à la place?
2
@Adel Cette sélection est en fait une sous-sélection dans une procédure stockée. Le tout est assez gros en fait, mais je suis sûr à 100% que c'est la partie exacte qui prend du temps à exécuter.
La modification du plan d'exécution, y compris les index sélectionnés automatiquement, est probablement liée à une modification des données. Je m'assurerais que vos index couvrent complètement ou le moteur prendra des chemins inattendus comme une analyse de table. Je suggère de revoir le plan d'exécution sur le nouveau serveur (sans conseils) pour voir où vous rencontrez des écarts par rapport au système d'origine.
Robert Miller
Je vois. Je n'ai changé que le serveur, la base de données est la même, avec les mêmes index. Pourtant, il ne semble pas avoir automatiquement choisi d'utiliser mes index. Il fait exactement ce que vous avez dit: un scan de table.
Smur
On dirait que l'opitmizer de requête n'aime aucun des index de la table pour votre requête. Le plan d'exécution a-t-il montré un index manquant?
Robert Miller

Réponses:

7

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 ALLAu lieu de LEFT JOIN. Tant que je pense que CROSS APPLYc'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 avez 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 des lignes de valeur nulle? Il suffit de remplacer LEFT JOINAvec INNER JOIN, je vous garantis que ce sera plus rapide.

à propos d'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 balors 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 "Look Up" . Cette procédure peut prendre beaucoup plus de temps que l'analyse de la table elle-même: "Analyse de table" .

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 Execution Plansi 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.


la source
1
Je ne peux pas changer cette jointure gauche en jointure intérieure, cela gâcherait les résultats, c'est une règle commerciale: la deuxième table ne doit pas nécessairement avoir un enregistrement connexe. En outre, la colonne de la clause WHERE n'accepte pas les valeurs nulles.
Smur
6

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.

Utilisation des jointures gauches à 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)

Utilisation d'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)
John Hartsock
la source
2

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

Conrad Frix
la source
2

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 pense, est la raison de sa lenteur. Si vous souhaitez vérifier cela, examinez le plan d'exécution.


la source
2

Les ThirdTableréférences (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

APPLICATION CROISÉE (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 qu'ils covering indexessont définis pour ThirdTable.SomeColumnet ThirdTable.SomeOtherColumnet que les index sont uniques. Cela signifie que vous devrez qualifier davantage les ThirdTableréférences pour éliminer la sélection de plusieurs lignes et améliorer les performances. Le choix de sub selects, LEFT JOINou CROSS APPLYn'aura pas vraiment d'importance jusqu'à ce que vous amélioriez la sélectivité pour ThirdTable.SomeColumnet ThirdTable.SomeOtherColumnen 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 indexsujet est joliment 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éliorez la sélectivité pour les requêtes ThirdTable.SomeColumnet ThirdTable.SomeOtherColumn(ou jointures) en ajoutant des colonnes dans la table associées pour garantir 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.

Robert Miller
la source