2016-10-19 4 views
4

Query объяснение:COUNT (1) + COUNT (DISTINCT()) гораздо медленнее, чем делать по 2 запросов отдельно

  • Person (идентифицируются PersonID) может иметь или не иметь соответствующий Job (идентифицированный JobID).
  • Если имеется соответствующий Job, связывание хранится в таблице PersonJob (PersonID < =>JobID).
  • Person без Job игнорируются.
  • Job также есть CityID.
  • За каждый Job.CityID, запрос хочет знать общее кол Person, а также подсчитывать уникальных Person.HouseID

Запрос:

SELECT 
    Job.CityID, COUNT(1) NumTotal, COUNT(DISTINCT(Person.HouseID)) NumDistinct 
FROM 
    Job 
    INNER JOIN PersonJob ON (PersonJob.JobID = Job.JobID) 
    INNER JOIN Person ON (Person.PersonID = PersonJob.PersonID) 
GROUP BY 
    Job.CityID 

Статистика:

  • SELECT COUNT(1) FROM PersonJob ~ 600,000
  • SELECT COUNT(1) FROM Person ~ 800.000
  • SELECT COUNT(DISTINCT(Person.HouseID)) FROM Person ~ 10,000
  • SELECT COUNT(1) FROM Job ~ 500
  • MS SQL Server 10,50

Проблема:

  • COUNT(1) часть запроса, при запуске отдельно, работает в 0.25sec.

    SELECT 
        Job.CityID, COUNT(1) NumTotal 
    FROM 
        Job 
        INNER JOIN PersonJob ON (PersonJob.JobID = Job.JobID) 
        INNER JOIN Person ON (Person.PersonID = PersonJob.PersonID) 
    GROUP BY 
        Job.CityID 
    
  • COUNT(DISTINCT(Person.HouseID)) часть запроса, при запуске отдельно, работает в 0.80sec.

    SELECT 
        Job.CityID, COUNT(DISTINCT(Person.HouseID)) NumDistinct 
    FROM 
        Job 
        INNER JOIN PersonJob ON (PersonJob.JobID = Job.JobID) 
        INNER JOIN Person ON (Person.PersonID = PersonJob.PersonID) 
    GROUP BY 
        Job.CityID 
    
  • Весь запрос выполняется в 3.10sec - 3 раза медленнее, почему?

планов выполнения:

  • Я не эксперт в чтении тех, извините.
  • Насколько я могу сказать, что проблема заключается в COUNT (DISTINCT)
  • При частичном запросе:
    • 25% матча Hash (агрегированной) (выход Job.CityID)
    • 15% матча Hash (Inner Регистрация) (выход Job.CityID, Person.HouseID)
      • 30% Индекс сканирования (выход Person.PersonID, Person.HouseID)
      • 14% Индекс искать (выход PersonJob.PersonID)
  • В полном запросе:
    • 03% матча Hash (Partial агрегатного) (выход Job.CityID, COUNT(*))
    • 31% совпадению Hash (совокупный) (выход Job.CityID)
    • 29% Столовая катушка (выход Job.CityID, Person.HouseID)
+0

В ваших сравнениях используется 'join'? –

+0

Не знаете, что вы имеете в виду, возможно, частичные запросы? Добавили их. – Codeguard

+0

Несвязанный, но: 'distinct' is *** NOT *** функция. 'distinct (Person.HouseID)' is ** точно ** то же самое, что и 'different Person.HouseID' –

ответ

4

Это известная проблема в версиях SQL Server до 2012

Вы могли бы попробовать это переписывание на основе on the code here.

WITH T1 
    AS (SELECT Job.CityID, 
       Person.HouseID 
     FROM Job 
       INNER JOIN PersonJob 
         ON (PersonJob.JobID = Job.JobID) 
       INNER JOIN Person 
         ON (Person.PersonID = PersonJob.PersonID)), 
    PartialSums 
    AS (SELECT COUNT(*) AS CountStarPartialCount, 
       HouseID, 
       CityID 
     FROM T1 
     GROUP BY CityID, 
        HouseID) 
SELECT CityID, 
     SUM(CountStarPartialCount) AS NumTotal, 
     COUNT(HouseID)    AS NumDistinct 
FROM PartialSums 
GROUP BY CityID 

SQL Server 2012 имеет некоторые улучшения в этой области. См. Is Distinct Aggregation Still Considered Harmful?

+2

Никогда не думал об этой особенности оптимизатора. Я узнал что-то новое сегодня. Спасибо. –

+0

Спасибо! По крайней мере, я знаю, что это проблема с MS SQL. Я вынес свое окончательное решение в качестве отдельного ответа. – Codeguard

1

После прочтения обходного пути, предоставленного Мартином Смитом, я решил, что обходное решение слишком сложно читать и понимать и станет полным беспорядком, если возникнет необходимость в дополнительной колонке DISTINCT. Я решил LEFT JOIN частичных запросов следующим образом:

SELECT 
    Job.CityID, NumTotal.Value, NumDistinct.Value 
FROM 
    Job 
    LEFT JOIN 
    (
    SELECT 
     Job.CityID, COUNT(1) AS Value 
    FROM 
     Job 
     INNER JOIN PersonJob ON (PersonJob.JobID = Job.JobID) 
     INNER JOIN Person ON (Person.PersonID = PersonJob.PersonID) 
    GROUP BY 
     Job.CityID 
) NumTotal ON (NumTotal.CityID = Job.CityID) 
    LEFT JOIN 
    (
    SELECT 
     Job.CityID, COUNT(DISTINCT Person.HouseID) AS Value 
    FROM 
     Job 
     INNER JOIN PersonJob ON (PersonJob.JobID = Job.JobID) 
     INNER JOIN Person ON (Person.PersonID = PersonJob.PersonID) 
    GROUP BY 
     Job.CityID 
) NumDistinct ON (NumDistinct.CityID = Job.CityID) 
GROUP BY 
    Job.CityID 

Это работает в 0.70sec, в то время как «обходной путь» SQL работает в 0.60sec. Это означает, что LEFT JOIN'inig в 5 раз быстрее, чем «исходный полный запрос», и только на 20% медленнее, чем «обходной путь», а его гораздо легче читать и расширять.