Por que vários COUNT são mais rápidos que um SUM com CASE?

14

Eu queria saber qual das duas abordagens a seguir é mais rápida:

1) Três 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) SUMcom FROM-cláusula:

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;

Fiquei surpreso que a diferença é tão grande. A primeira consulta com três subconsultas retorna o resultado imediatamente, enquanto a segunda SUMabordagem precisa de 18 segundos.

Claimsé uma exibição que seleciona de uma tabela contendo ~ 18 milhões de linhas. Há um índice na coluna FK da ClaimStatustabela que contém o nome do status.

Por que faz uma diferença tão grande se eu uso COUNTou SUM?

Planos de execução:

Existem 12 status no total. Esses três status pertencem a 7% de todas as linhas.


Esta é a visão real, não tenho certeza se é relevante:

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
fonte
Parece que os dois links apontam para a COUNTversão do plano. Você pode editar o gosto na SUMversão para apontar para o plano correto?
Geoff Patterson
Qual é a proporção de linhas com esses três status em comparação com as linhas com outros status?
Max Vernon
1
@ MaxVernon: sim, é claro, eu já vi muitos zeros, você está certo. Deixe-me excluir meus comentários. Sim, existem 16,7 milhões de linhas em outro status. A maioria é Authorized.
perfil completo de Tim Schmelter
2
Eu estimaria que o segundo plano está sofrendo por ter que digitalizar a tabela inteira 12 vezes (é o que é mostrado). Provavelmente, isso decorre de não conseguir empurrar os predicados para a varredura. Como é o desempenho se você adicionar WHERE c.Status = 'Approved' or c.Status = 'Valid' or c.status = 'Reject'à SUMvariante.
Max Vernon
@ MaxVernon: existem doze status no total. Não é realmente um problema para mim, mas fiquei muito surpreso que o otimizador não possa lidar com isso. Eu realmente deveria trabalhar nas minhas habilidades de análise do plano de execução. Faça disso uma resposta. Qual é sua suposição, por que o SQL-Server não consegue verificar apenas três status?
Tim Schmelter

Respostas:

19

A COUNT(*)versão pode simplesmente procurar o índice que você possui na coluna de status uma vez para cada status selecionado, enquanto a SUM(...)versão precisa buscar o índice doze vezes (o número total de tipos de status exclusivos).

Claramente, procurar um índice três vezes será mais rápido do que procurá-lo 12 vezes.

O primeiro plano requer uma concessão de memória de 238MB, enquanto o segundo plano requer uma concessão de memória de 650MB. Ele pode ser que a concessão de memória maior não poderia ser imediatamente preenchido, tornando a consulta que muito mais lento.

Altere a segunda consulta para ser:

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

Isso permitirá que o otimizador de consultas elimine 75% da procura do índice e deve resultar em uma concessão de memória necessária mais baixa, em requisitos de E / S mais baixos e no tempo de resultado mais rápido.

A SUM(CASE WHEN ...)construção essencialmente impede que o otimizador de consulta envie os Statuspredicados para baixo na parte de busca de índice do plano.

Max Vernon
fonte
Boa captura com a memória. Percebi que todos os meus 32 GB estão em uso no momento (apenas 300 MB gratuitos). Editar No entanto, liberei memória. O resultado é o mesmo
Tim Schmelter 3/15/15
Você pode querer olhar para a max server memoryopção - ela deve ser configurada com o valor correto para o seu sistema. Você pode examinar esta pergunta e as respostas para obter detalhes sobre como fazer isso.
Max Vernon
1
Infelizmente, este servidor não é usado apenas para o banco de dados, mas também para um cubo SSAS e algumas ferramentas (incluindo o aplicativo Web da intranet). Mas eu já atribui 12 GB no máximo.
precisa saber é o seguinte