UNION ALL Производительность в SQL Server 2005 - PullRequest
4 голосов
/ 05 февраля 2010

У меня есть запрос с длинной цепочкой CTE, который заканчивается на

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets
UNION ALL
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryRegions

Время выполнения этого запроса составляет 1450 мс. Когда я выполняю эти 2 SELECT по отдельности, это занимает гораздо меньше времени. Для запроса

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets

время выполнения составляет 106 мс. И для запроса

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryRegions

это 20 мс.

Почему UNION ALL увеличивает время выполнения более чем в 10 раз? Что я могу сделать, чтобы уменьшить его?

Спасибо за вашу помощь.

ОБНОВЛЕНО Весь запрос (я сократил его, но проблема все еще присутствует):

WITH tFoundRegions AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 1) > 0
),
tFoundAreas AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 2) > 0
),
tFoundCities AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 4) > 0
),
tFoundSubCities AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 8) > 0
),
tFoundStreets AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 16) > 0
),
tDictionaryStreets AS
(
    SELECT DISTINCT
        CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE NULL END RegionName
      , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE NULL END AreaName
      , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE NULL END CityName
      , CASE WHEN SubCityName  IN (SELECT KladrItemName FROM tFoundSubCities) THEN SubCityName ELSE NULL END SubCityName
      , StreetName 
    FROM StreetNames
    WHERE StreetName IN (SELECT KladrItemName FROM tFoundStreets)
),
tMissingSubCities AS
(
    SELECT KladrItemName FROM tFoundSubCities
    WHERE KladrItemName NOT IN (SELECT SubCityName FROM tDictionaryStreets)
),
tDictionarySubCities AS
(
    SELECT DISTINCT 
        CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE NULL END RegionName
      , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE NULL END AreaName
      , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE NULL END CityName
      , SubCityName
      , NULL StreetName 
    FROM SubCityNames
    WHERE SubCityName IN (SELECT KladrItemName FROM tMissingSubCities)
)
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets
UNION ALL
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionarySubCities

Ответы [ 4 ]

3 голосов
/ 05 февраля 2010

Убедитесь, что вы очищаете кэши выполнения + данных между каждым запуском теста.

, например

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS

Если вы сначала запускаете с UNION ALL, а затем запускаете 2 выбора по отдельности, то данные уже будут кэшироваться в памяти, что значительно повышает производительность (поэтому создается ложное впечатление, что последующий подход быстрее, когда он может быть ).

Если вы использовали UNION, то это может быть медленнее, так как он должен применять DISTINCT, но UNION ALL не обязан это делать, поэтому он не должен отличаться.

Обновление:
Посмотрите на планы выполнения и сравните их - посмотрите, есть ли разница. План выполнения можно просмотреть, нажав кнопку «Включить фактический план выполнения» в SSMS перед выполнением запроса

Обновление 2:
Основываясь на полных данных CTE, я думаю, что я буду смотреть на их оптимизацию - я не думаю, что UNION ALL на самом деле является проблемой.

ИМХО, лучше всего попробовать поочередно проработать CTE и попытаться оптимизировать каждый из них по отдельности, чтобы при объединении их всех в основном запросе они работали лучше.

например. для tDictionaryStreets, как насчет того, чтобы попробовать это:

SELECT DISTINCT
    r.KladrItemName AS RegionName,
        a.KladrItemName AS AreaName,
        c.KladrItemName AS CityName,
        sc.KladrItemName AS SubCityName,
        s.StreetName      
FROM StreetNames s
    JOIN tFoundStreets fs ON s.StreetName = fs.KladrItemName
    LEFT JOIN tFoundRegions r ON s.RegionName = r.KladrItemName
    LEFT JOIN tFoundAreas a ON s.AreaName = a.KladrItemName
    LEFT JOIN tFoundCities c ON s.CityName = c.KladrItemName
    LEFT JOIN tFoundSubCities sc ON s.SubCityName = scc.KladrItemName

KladrItemName для каждой таблицы должен по крайней мере иметь индекс. Попробуйте переработать tDictionarySubCities таким же способом, что и соединения.

0 голосов
/ 08 февраля 2012

Я столкнулся с подобной проблемой, и после тщательного анализа ситуации мне кажется, что использование cte в запросе UNION ALL отключает распараллеливание (что, скорее всего, является ошибкой).

Иными словами, UNION ALL будет равен сумме двух запросов, для каждого из которых установлено значение (maxdop 1).

Несмотря на то, что необходимо провести дополнительное тестирование, и на самом деле трудно создать запрос, который будет использовать распараллеливание, чтобы иметь возможность проверить или даже отправить в виде ошибки в Microsoft Connect, все же ваша проблема, а также проблема, описанная в Почему CTE (рекурсивный анализ) не парализован (MAXDOP = 8)? также является доказательством того, что на самом деле такая проблема существует.

РЕДАКТИРОВАТЬ: Я провел более тщательное тестирование, и, хотя UNION ALL выполняет параллелизацию много раз, все же существуют ситуации, когда без UNION ALL он распараллеливается, но если UNION ALL отключает его.

Хотя это может быть ошибкой, это также может быть результатом того, что оптимизатор запросов не ищет план best , а вместо этого ищет план good и поскольку два запроса, объединенные с UNION, уже генерируют сложные планы, а также запрос с CTE, он может просто найти хороший план, даже прежде чем рассматривать возможность распараллеливания.

0 голосов
/ 05 февраля 2010

Может быть сеть (маловероятно) или память. В зависимости от количества строк каждый набор результатов возвращает. Один из способов проверить, является ли он сетевым или серверным, состоит в том, чтобы включить статистику клиента в SSMS (Запрос - Включить статистику клиента - SHIFT-ALT-S). Внизу вы можете определить, где большая часть времени проводится.

Не могли бы вы сравнить планы выполнения? [...] lmsasu [...] Когда запрос выполняется быстро, он использует «объединение слиянием», а медленный - «вложенные циклы». [...]

Пока не могу комментировать, но в плане выполнения вы видите разницу между «объединением» двух наборов результатов (объединение слиянием) и операцией RBAR (произношение reebar - Row By Agonizing Row [Jeff Moden]), обычно называемой петля.

Объединение слиянием: SQL находит два набора результатов с общей ссылкой и выполняет операцию на основе набора для объединения двух наборов. Вложенный цикл: SQL не может найти общую ссылку и объединяет одну строку из набора 1 во все строки из набора 2 строка за строкой и отбрасывает те, которые не совпадают.

Суть в том, что SQL сталкивается с пустыми результатами, которые являются неизвестными. Попробуйте назначить значение типа «XYZ» (или что-либо, о чем известно, что оно не появлялось), которое можно просто отфильтровать в последнем запросе Это может избежать вложенного цикла в определенных результирующих наборах, поскольку значения определены, а не неизвестны. Аналогично:

[...]
tDictionarySubCities AS 
( 
    SELECT DISTINCT  
        CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE 'XYZXYZ' END RegionName 
      , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE 'XYZXYZ' END AreaName 
      , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE 'XYZXYZ' END CityName 
      , SubCityName 
      , NULL StreetName  
    FROM SubCityNames 
    WHERE SubCityName IN (SELECT KladrItemName FROM tMissingSubCities) 
) 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName  
FROM tDictionaryStreets 
WHERE RegionName <> 'XYZ'
UNION ALL 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName  
FROM tDictionarySubCities 
WHERE RegionName <> 'XYZ'
0 голосов
/ 05 февраля 2010

Не могли бы вы сравнить планы выполнения? Какая разница? «Объединение всех» должно работать нормально, так как удаление дубликатов не требуется (это требует сортировки, которая является дорогостоящей для больших наборов данных).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...