Чому кілька разів швидше, ніж один СУМ з 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-клаузою:

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 ГБ як максимум.
Тім Шмелтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.