Почему несколько COUNT быстрее, чем одна сумма с CASE?

14

Я хотел знать, какой из следующих двух подходов быстрее:

1) Три 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) SUMс 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;

Я был удивлен, что разница такая большая. Первый запрос с тремя подзапросами возвращает результат немедленно, тогда как второй SUMподход требует 18 секунд.

Claimsэто представление, которое выбирает из таблицы, содержащей ~ 18 миллионов строк. В столбце FK есть указатель на ClaimStatusтаблицу, в которой содержится имя-статуса.

Почему это так важно, использую ли я COUNTили SUM?

Исполнение-планы:

Всего 12 статусов. Эти три статуса принадлежат 7% всех строк.


Это фактический вид, я не уверен, что это актуально:

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 
Тим Шмельтер
источник
Похоже, обе ссылки указывают на COUNTверсию плана. Можете ли вы отредактировать подобное в SUMверсии, чтобы указать правильный план?
Джефф Паттерсон
Каково соотношение строк с этими тремя состояниями по сравнению с рядами с другими состояниями?
Макс Вернон
1
@MaxVernon: да, конечно, я видел слишком много нулей, ты прав. Позвольте мне удалить мои комментарии. Да, в другом статусе 16,7 миллиона строк. Большинство из них Authorized.
Тим Шмелтер
2
Я предполагаю, что второй план страдает от необходимости сканировать всю таблицу 12 раз (это то, что показывает). Вероятно, это происходит из-за невозможности протолкнуть предикаты в сканирование. Какова производительность, если вы добавите WHERE c.Status = 'Approved' or c.Status = 'Valid' or c.status = 'Reject'в SUMвариант.
Макс Вернон
@MaxVernon: всего двенадцать статусов. Это не проблема для меня, но я был очень удивлен, что оптимизатор не может справиться с этим. Я должен действительно работать над моими навыками анализа плана выполнения. Сделай это ответом. Как вы думаете, почему SQL-сервер не может сканировать только три состояния?
Тим Шмелтер

Ответы:

19

COUNT(*)Версия может просто искать в индекс у вас на колонке статуса один раз для каждого состояния , вы выбираете, в то время как SUM(...)потребности версии для взыщут индексировать двенадцать раз (общее количество уникальных статусов).

Очевидно, что поиск индекса в три раза будет быстрее, чем поиск в 12 раз.

Первый план требует предоставления памяти в 238 МБ, тогда как второй план требует предоставления памяти в 650 МБ. Это может быть , что большая субсидия память не может быть немедленно заполнен, делая запрос , который гораздо медленнее.

Измените второй запрос:

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

Это позволит оптимизатору запросов исключить 75% поисков по индексу, что должно привести как к меньшему требуемому предоставлению памяти, так и к меньшим требованиям ввода-вывода и более быстрому получению времени на результат.

Эта SUM(CASE WHEN ...)конструкция по существу не позволяет оптимизатору запросов проталкивать Statusпредикаты в часть плана поиска индекса.

Макс Вернон
источник
Хороший улов с памятью. Я заметил, что все мои 32 ГБ в настоящее время используются (только 300 МБ бесплатно). Редактировать Однако, я освободил немного памяти. Результат тот же
Тим Шмельтер
Возможно, вы захотите посмотреть на max server memoryопцию - она ​​должна быть настроена на правильное значение для вашей системы. Вы можете посмотреть на этот вопрос и ответы на него, чтобы узнать, как это сделать.
Макс Вернон
1
К сожалению, этот сервер используется не только для базы данных, но также для куба SSAS и некоторых инструментов (включая веб-приложение для интрасети). Но я уже назначил 12 ГБ как максимум.
Тим Шмелтер