Perché più COUNT sono più veloci di un SUM con CASE?


14

Volevo sapere quale dei due seguenti approcci è più veloce:

1) Tre 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) SUMcon 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;

Sono rimasto sorpreso dal fatto che la differenza sia così grande. La prima query con tre sottoquery restituisce immediatamente il risultato mentre il secondo SUMapproccio richiede 18 secondi.

Claimsè una vista che seleziona da una tabella contenente ~ 18 milioni di righe. C'è un indice sulla colonna FK nella ClaimStatustabella che contiene il nome-stato.

Perché fa una così grande differenza se uso COUNTo SUM?

Esecuzione-piani:

Ci sono 12 stati in totale. Questi tre stati appartengono al 7% di tutte le righe.


Questa è la visione reale, non sono sicuro che sia 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 

Sembra che entrambi i collegamenti puntino alla COUNTversione del piano. Puoi modificare il mi piace alla SUMversione per puntare al piano corretto?
Geoff Patterson,

Qual è il rapporto tra le file con questi tre statii rispetto alle file con altri statii?
Max Vernon,

1
@MaxVernon: sì, certo, ho visto troppi zeri, hai ragione. Vorrei cancellare i miei commenti. Sì, ci sono 16,7 milioni di righe in altro stato. Molti lo sono Authorized.
Tim Schmelter,

2
Stimerei che il secondo piano stia soffrendo di dover scansionare l'intero tavolo 12 volte (questo è ciò che viene mostrato). Questo probabilmente deriva dal fatto di non essere in grado di spingere i predicati nella scansione. Qual è la prestazione se aggiungi WHERE c.Status = 'Approved' or c.Status = 'Valid' or c.status = 'Reject'alla SUMvariante.
Max Vernon,

@MaxVernon: ci sono dodici stati in totale. Non è davvero un problema per me, ma ero molto sorpreso che l'ottimizzatore non potesse gestirlo. Dovrei davvero lavorare sulle mie capacità di analisi del piano di esecuzione. Dagli una risposta. Qual è il tuo presupposto, perché SQL Server non è in grado di scansionare solo tre stati?
Tim Schmelter,

Risposte:


19

La COUNT(*)versione è in grado di cercare semplicemente l'indice presente nella colonna dello stato una volta per ogni stato selezionato, mentre la SUM(...)versione deve cercare l'indice dodici volte (il numero totale di tipi di stato univoci).

Chiaramente cercare un indice tre volte sarà più veloce che cercarlo 12 volte.

Il primo piano richiede una concessione di memoria di 238 MB, mentre il secondo piano richiede una concessione di memoria di 650 MB. È possibile che la concessione di memoria più grande non possa essere riempita immediatamente, rendendo la query molto più lenta.

Modificare la seconda query in modo che sia:

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';

Ciò consentirà a Query Optimizer di eliminare il 75% delle ricerche dell'indice e dovrebbe comportare sia una concessione di memoria richiesta inferiore, requisiti di I / O inferiori, sia tempi di risposta più rapidi.

Il SUM(CASE WHEN ...)costrutto essenzialmente impedisce a Query Optimizer di spingere i Statuspredicati verso il basso nella parte di ricerca dell'indice del piano.


Bella cattura con il ricordo. Ho notato che tutti i miei 32 GB sono attualmente in uso (solo 300 MB gratuiti). Modifica Tuttavia, ho liberato un po 'di memoria. Il risultato è lo stesso
Tim Schmelter,

Potresti voler esaminare l' max server memoryopzione: dovrebbe essere configurata sul valore corretto per il tuo sistema. Potresti voler esaminare questa domanda e le risposte per i dettagli su come farlo.
Max Vernon,

1
Sfortunatamente questo server non viene utilizzato solo per il database ma anche per un cubo SSAS e alcuni strumenti (inclusa l'app Web Intranet). Ma ho già assegnato 12 GB al massimo.
Tim Schmelter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.