Pourquoi plusieurs COUNT sont plus rapides qu'un SUM avec CASE?

14

Je voulais savoir laquelle des deux approches suivantes est la plus rapide:

1) Trois COUNT:

 SELECT Approved = (SELECT COUNT(*) FROM dbo.Claims d
                  WHERE d.Status = 'Approved'),
        Valid    = (SELECT COUNT(*) FROM dbo.Claims d
                    WHERE d.Status = 'Valid'),
        Reject   = (SELECT COUNT(*) FROM dbo.Claims d
                    WHERE d.Status = 'Reject')

2) SUMavec FROM-clause:

SELECT  Approved = SUM(CASE WHEN Status = 'Approved' THEN 1 ELSE 0 END),
        Valid    = SUM(CASE WHEN Status = 'Valid'    THEN 1 ELSE 0 END),
        Reject   = SUM(CASE WHEN Status = 'Reject'   THEN 1 ELSE 0 END)
FROM dbo.Claims c;

J'ai été surpris que la différence soit si grande. La première requête avec trois sous-requêtes renvoie le résultat immédiatement tandis que la deuxième SUMapproche nécessite 18 secondes.

Claimsest une vue qui sélectionne dans un tableau contenant environ 18 millions de lignes. Il y a un index sur la colonne FK de la ClaimStatustable qui contient le nom d'état.

Pourquoi est-ce si important que j'utilise COUNTou SUM?

Plans d'exécution:

Il y a 12 statuts au total. Ces trois statuts appartiennent à 7% de toutes les lignes.


Ceci est la vue réelle, je ne sais pas si elle est pertinente:

CREATE VIEW [dbo].[Claims]
AS
SELECT 
   mu.Marketunitname AS MarketUnit, 
   c.Countryname     AS Country, 
   gsp.Gspname       AS GSP, 
   gsp.Wcmskeynumber AS GspNumber, 
   sl.Slname         AS SL, 
   sl.Wcmskeynumber  AS SlNumber, 
   m.Modelname       AS Model, 
   m.Salesname       AS [Model-Salesname], 
   s.Claimstatusname AS [Status], 
   d.Work_order      AS [Work Order], 
   d.Ssn_number      AS IMEI, 
   d.Ssn_out, 
   Remarks, 
   d.Claimnumber     AS [Claim-Number], 
   d.Rma_number      AS [RMA-Number], 
   dbo.ToShortDateString(d.Received_Date, 1) AS [Received Date], 
   Iddata, 
   Fisl, 
   Fimodel, 
   Ficlaimstatus 
FROM Tabdata AS d 
   INNER JOIN Locsl AS sl 
           ON d.Fisl = sl.Idsl 
   INNER JOIN Locgsp AS gsp 
           ON sl.Figsp = gsp.Idgsp 
   INNER JOIN Loccountry AS c 
           ON gsp.Ficountry = c.Idcountry 
   INNER JOIN Locmarketunit AS mu 
           ON c.Fimarketunit = mu.Idmarketunit 
   INNER JOIN Modmodel AS m 
           ON d.Fimodel = m.Idmodel 
   INNER JOIN Dimclaimstatus AS s 
           ON d.Ficlaimstatus = s.Idclaimstatus 
   INNER JOIN Tdefproducttype 
           ON d.Fiproducttype = Tdefproducttype.Idproducttype 
   LEFT OUTER JOIN Tdefservicelevel 
                ON d.Fimaxservicelevel = Tdefservicelevel.Idservicelevel 
   LEFT OUTER JOIN Tdefactioncode AS ac 
                ON d.Fimaxactioncode = ac.Idactioncode 
Tim Schmelter
la source
Il semble que les deux liens pointent vers la COUNTversion du plan. Pouvez-vous modifier la SUMversion similaire pour pointer vers le bon plan?
Geoff Patterson
Quel est le rapport des lignes avec ces trois statii par rapport aux lignes avec d'autres statii?
Max Vernon
1
@MaxVernon: oui, bien sûr, j'ai vu trop de zéros, tu as raison. Permettez-moi de supprimer mes commentaires. Oui, il y a 16,7 millions de lignes dans un autre statut. La plupart le sont Authorized.
Tim Schmelter
2
Je dirais que le deuxième plan souffre d'avoir à scanner la table entière 12 fois (c'est ce qui est montré). Cela vient probablement de l'impossibilité de pousser les prédicats dans l'analyse. À quoi ressemblent les performances si vous ajoutez WHERE c.Status = 'Approved' or c.Status = 'Valid' or c.status = 'Reject'à la SUMvariante.
Max Vernon
@MaxVernon: il y a douze statuts au total. Ce n'est pas vraiment un problème pour moi, mais j'ai été très surpris que l'optimiseur ne puisse pas gérer cela. Je devrais vraiment travailler sur mes compétences d'analyse de plan d'exécution. Faites-en une réponse. Quelle est votre hypothèse, pourquoi SQL-Server ne peut-il analyser que trois statuts?
Tim Schmelter du

Réponses:

19

La COUNT(*)version peut simplement rechercher l'index que vous avez dans la colonne d'état une fois pour chaque état que vous sélectionnez, tandis que la SUM(...)version doit rechercher l'index douze fois (le nombre total de types d'état uniques).

Il est clair que la recherche d'un indice trois fois sera plus rapide que sa recherche 12 fois.

Le premier plan nécessite une allocation de mémoire de 238 Mo, tandis que le second plan nécessite une allocation de mémoire de 650 Mo. Il se peut que la plus grande allocation de mémoire ne puisse pas être remplie immédiatement, ce qui rend la requête beaucoup plus lente.

Modifiez la deuxième requête pour qu'elle soit:

SELECT  Approved = SUM(CASE WHEN Status = 'Approved' THEN 1 ELSE 0 END),
        Valid    = SUM(CASE WHEN Status = 'Valid'    THEN 1 ELSE 0 END),
        Reject   = SUM(CASE WHEN Status = 'Reject'   THEN 1 ELSE 0 END)
FROM dbo.Claims c
WHERE c.Status = 'Approved'
    OR c.Status = 'Valid'
    OR c.Status = 'Reject';

Cela permettra à l'optimiseur de requêtes d'éliminer 75% des recherches d'index, et devrait entraîner à la fois une allocation de mémoire requise inférieure, des exigences d'E / S plus faibles et un temps de résultat plus rapide.

La SUM(CASE WHEN ...)construction empêche essentiellement l'optimiseur de requêtes de pousser les Statusprédicats vers le bas dans la partie de recherche d'index du plan.

Max Vernon
la source
Belle prise avec la mémoire. J'ai remarqué que tous mes 32 Go sont actuellement utilisés (seulement 300 Mo gratuits). Modifier Cependant, j'ai libéré de la mémoire. Le résultat est le même
Tim Schmelter
Vous voudrez peut-être examiner l' max server memoryoption - elle doit être configurée à la valeur correcte pour votre système. Vous voudrez peut-être regarder cette question et les réponses pour plus de détails sur la façon de le faire.
Max Vernon
1
Malheureusement, ce serveur n'est pas seulement utilisé pour la base de données, mais aussi pour un cube SSAS et certains outils (y compris l'application Web intranet). Mais j'ai déjà attribué 12 Go au maximum.
Tim Schmelter du