2010-02-05 7 views
4

J'ai une requête avec une longue chaîne de CTEs qui se termine parUNION ALL performance dans SQL Server 2005

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets 
UNION ALL 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryRegions 

Le temps d'exécution de cette requête est 1450 ms. Lorsque j'exécute ces 2 SELECT séparément, cela prend beaucoup moins de temps. Pour la requête

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets 

le temps d'exécution est de 106 ms. Et pour la requête

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryRegions 

c'est 20 ms. Pourquoi UNION ALL augmente le temps d'exécution plus de 10 fois? Que puis-je faire pour le diminuer?

Nous vous remercions de votre aide.

MISE À JOUR La requête tout (j'écourté, mais le problème présente encore) est

WITH tFoundRegions AS 
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants 
    WHERE UserID = @UserID AND (indeces & 1) > 0 
), 
tFoundAreas AS 
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants 
    WHERE UserID = @UserID AND (indeces & 2) > 0 
), 
tFoundCities AS 
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants 
    WHERE UserID = @UserID AND (indeces & 4) > 0 
), 
tFoundSubCities AS 
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants 
    WHERE UserID = @UserID AND (indeces & 8) > 0 
), 
tFoundStreets AS 
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants 
    WHERE UserID = @UserID AND (indeces & 16) > 0 
), 
tDictionaryStreets AS 
(
    SELECT DISTINCT 
     CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE NULL END RegionName 
     , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE NULL END AreaName 
     , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE NULL END CityName 
     , CASE WHEN SubCityName IN (SELECT KladrItemName FROM tFoundSubCities) THEN SubCityName ELSE NULL END SubCityName 
     , StreetName 
    FROM StreetNames 
    WHERE StreetName IN (SELECT KladrItemName FROM tFoundStreets) 
), 
tMissingSubCities AS 
(
    SELECT KladrItemName FROM tFoundSubCities 
    WHERE KladrItemName NOT IN (SELECT SubCityName FROM tDictionaryStreets) 
), 
tDictionarySubCities AS 
(
    SELECT DISTINCT 
     CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE NULL END RegionName 
     , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE NULL END AreaName 
     , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE NULL END CityName 
     , SubCityName 
     , NULL StreetName 
    FROM SubCityNames 
    WHERE SubCityName IN (SELECT KladrItemName FROM tMissingSubCities) 
) 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets 
UNION ALL 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionarySubCities 
+0

ces temps d'exécution changent-ils si vous exécutez les mêmes requêtes plusieurs fois? – devio

+0

Oui, mais pas de manière significative. Anyway diffrence est environ 10 fois – StuffHappens

+0

Voir la mise à jour 2 dans ma réponse, basée sur des informations supplémentaires que vous avez donné – AdaTheDev

Répondre

3

videz l'exécution + caches de données entre chaque essai.

par exemple.

DBCC FREEPROCCACHE 
DBCC DROPCLEANBUFFERS 

Si vous exécutez avec l'Union Tout d'abord, puis exécutez le 2 sélectionne séparément après, les données seront déjà mises en mémoire cache rendant bien meilleure performance (donc donner l'impression fausse que l'approche suivante est plus rapide lorsque ce n'est peut-être pas).

Si vous avez utilisé un UNION, cela peut être plus lent car il doit appliquer un DISTINCT, mais UNION ALL n'a pas à faire cela, donc cela ne devrait pas être différent.

Mise à jour:
Jetez un oeil sur les plans d'exécution et de les comparer - voir s'il y a une différence. Vous pouvez consulter le plan d'exécution en cliquant sur le « Inclure plan d'exécution réel » bouton dans SSMS avant d'exécuter la requête

Mise à jour 2:
Basé sur CTEs plein donné, je pense que je serais à la recherche à optimiser les - Je ne pense pas que l'UNION ALL soit réellement le problème.

À mon humble avis, la meilleure chose à essayer est de travailler à travers les CTE un par un et essayer d'optimiser chacun individuellement de sorte que lorsque vous les combinez tous dans la requête principale, ils fonctionnent mieux.

par exemple. pour tDictionaryStreets, que diriez-vous d'essayer ceci:

SELECT DISTINCT 
    r.KladrItemName AS RegionName, 
     a.KladrItemName AS AreaName, 
     c.KladrItemName AS CityName, 
     sc.KladrItemName AS SubCityName, 
     s.StreetName  
FROM StreetNames s 
    JOIN tFoundStreets fs ON s.StreetName = fs.KladrItemName 
    LEFT JOIN tFoundRegions r ON s.RegionName = r.KladrItemName 
    LEFT JOIN tFoundAreas a ON s.AreaName = a.KladrItemName 
    LEFT JOIN tFoundCities c ON s.CityName = c.KladrItemName 
    LEFT JOIN tFoundSubCities sc ON s.SubCityName = scc.KladrItemName 

KladrItemName sur chaque table devrait au moins avoir un index. Essayez de retravailler tDictionarySubCities de la même manière avec des jointures.

+0

J'ai essayé d'exécuter la requête un grand nombre de fois. L'heure d'exécution change de manière insignifiante. – StuffHappens

+0

Mise à jour ajoutée. Aussi, êtes-vous en train d'exécuter les 2 SELECT lorsque vous comparez les performances ou exécutez-vous l'ensemble de la requête que vous mentionnez (avec de nombreux CTE)? c'est-à-dire que cela pourrait être quelque chose d'autre dans la requête par opposition à cette partie UNION ALL spécifique – AdaTheDev

+0

Le plan d'exécution est énorme. Je ne peux pas le lire du tout ... Mais il y a une différence: quand la requête s'exécute rapidement, elle utilise 'Merge join' quand elle est lente - 'loops imbriquées'. Qu'est-ce que ça veut dire? que puis-je faire pour qu'il utilise le même plan? – StuffHappens

0

Pourriez-vous comparer les plans d'exécution? Qu'est-ce qui est différent? "Union all" devrait fonctionner correctement, car il n'y a pas de suppression en double (cela nécessite un tri, ce qui est coûteux pour de grands ensembles de données).

+0

Le plan d'exécution est énorme. Lorsque la requête s'exécute rapidement, elle utilise 'fusionner la jointure', lorsqu'elle est lente - 'boucles imbriquées'. Qu'est-ce que ça veut dire? – StuffHappens

0

Peut être en réseau (peu probable) ou en mémoire.En fonction du nombre de lignes ramenées par chaque jeu de résultats. Une façon de vérifier s'il s'agit d'un réseau ou du serveur consiste à inclure des statistiques client dans SSMS (Requête - Inclure les statistiques client - SHIFT-ALT-S). En bas, vous pouvez différencier l'endroit où l'essentiel du temps est passé.

Pourriez-vous comparer les plans d'exécution? [...] lmsasu [...] Lorsque la requête exécute rapidement, il utilise « jointure de fusion », quand lent -. « Boucles imbriquées » [...]

ne peux pas commenter, mais encore ce que vous voyez dans le plan d'exécution, il y a la différence entre "joindre" deux ensembles de résultats (fusionner une jointure) et une opération RBAR (prononcer reebar - Row par Agonizing Row [Jeff Moden]), communément appelée une boucle. Fusionner Jointure: SQL trouve deux ensembles de résultats avec un lien commun et effectue une opération basée sur un ensemble pour rapprocher les deux ensembles. Boucle imbriquée: SQL ne peut pas trouver un lien commun et joint une ligne de l'ensemble 1 à toutes les lignes de l'ensemble 2 ligne par ligne et rejette celles qui ne correspondent pas. L'intuition est que le SQL trébuche sur les résultats NULL qui sont des résultats inconnus. Essayez d'attribuer une valeur comme "XYZ" (ou tout autre élément connu pour ne pas apparaître) que vous pouvez simplement filtrer dans la dernière requête. Cela pourrait éviter la boucle imbriquée dans certains ensembles de résultats car les valeurs sont déterminées et non inconnues. similaires à:

[...] 
tDictionarySubCities AS 
( 
    SELECT DISTINCT 
     CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE 'XYZXYZ' END RegionName 
     , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE 'XYZXYZ' END AreaName 
     , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE 'XYZXYZ' END CityName 
     , SubCityName 
     , NULL StreetName 
    FROM SubCityNames 
    WHERE SubCityName IN (SELECT KladrItemName FROM tMissingSubCities) 
) 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets 
WHERE RegionName <> 'XYZ' 
UNION ALL 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionarySubCities 
WHERE RegionName <> 'XYZ' 
+0

Le résultat est inférieur à 500 lignes. Aucun réseau n'est utilisé. – StuffHappens

0

Je trébuché sur un problème similaire et après mûre analyse de la situation, il me semble que l'utilisation d'un cte dans une requête UNION ALL désactive parallélisation (ce qui est très probablement un bug). En d'autres termes, UNION ALL sera égal à la somme des deux requêtes où chacune a été définie (maxdop 1).

Alors qu'il doit y avoir plus de tests et qu'il est réellement difficile de faire une requête qui utilisera la parallélisation pour pouvoir tester ou même soumettre comme un bug à Microsoft Connect, toujours votre problème ainsi que le problème décrit dans Why CTE (recurisve) is not parallilized (MAXDOP=8)? sont également la preuve qu'il existe réellement un tel problème. EDIT: J'ai testé plus longuement, et bien qu'un UNION ALL se parallélise plusieurs fois, il y a quand même des situations où un UNION ALL sans paralléliser mais un UNION ALL l'éteint.

Bien que cela puisse être un bogue, il peut aussi être le résultat du fait que l'optimiseur de requête ne cherche pas le meilleur plan et au lieu qu'il recherche un bon plan, et depuis deux requêtes jointe à UNION génère déjà des plans complexes ainsi qu'une requête avec un CTE, il pourrait simplement trouver un bon plan avant même d'envisager l'option de la parallélisation.