План был скомпилирован на экземпляре окончательной первоначальной версии SQL Server 2008 R2 (сборка 10.50.1600). Вам следует установить Service Pack 3 (сборка 10.50.6000), а затем установить последние исправления, чтобы привести его к (текущей) последней сборке 10.50.6542. Это важно по ряду причин, включая безопасность, исправление ошибок и новые функции.
Оптимизация встраивания параметров
Относящийся к настоящему вопросу, SQL Server 2008 R2 RTM не поддерживает оптимизацию встраивания параметров (PEO) для OPTION (RECOMPILE)
. Прямо сейчас вы оплачиваете стоимость перекомпиляции без реализации одного из основных преимуществ.
Когда PEO доступен, SQL Server может использовать литеральные значения, хранящиеся в локальных переменных и параметрах, непосредственно в плане запроса. Это может привести к значительным упрощениям и повышению производительности. Более подробная информация об этом содержится в моей статье « Параметры сниффинга», «Встраивание» и «Параметры RECOMPILE» .
Хэш, сортировка и разливы
Они отображаются только в планах выполнения, когда запрос был скомпилирован в SQL Server 2012 или более поздней версии. В более ранних версиях нам приходилось отслеживать разливы во время выполнения запроса с использованием Profiler или Extended Events. Разливы всегда приводят к физическому вводу-выводу в (и из) постоянной базы данных хранилища данных , что может иметь важные последствия для производительности, особенно если разлив велик или путь ввода-вывода находится под давлением.
В вашем плане выполнения есть два оператора Hash Match (Aggregate). Память, зарезервированная для хеш-таблицы, основана на оценке выходных строк (другими словами, она пропорциональна количеству групп, найденных во время выполнения). Предоставленная память фиксируется непосредственно перед началом выполнения и не может увеличиваться во время выполнения, независимо от того, сколько свободной памяти имеет экземпляр. В предоставленном плане оба оператора Hash Match (Aggregate) производят больше строк, чем ожидал оптимизатор, и поэтому могут возникать разливы в базу данных tempdb во время выполнения.
В плане также есть оператор Hash Match (Inner Join). Память, зарезервированная для хеш-таблицы, основана на оценке входных строк на стороне зонда . Входные данные датчика оценивают 847 399 строк, но во время выполнения встречаются 1223 636 строк. Этот избыток также может вызывать разлив хеша.
Избыточный агрегат
Hash Match (Aggregate) на узле 8 выполняет операцию группировки (Assortment_Id, CustomAttrID)
, но входные строки равны выходным строкам:
Это говорит о том, что комбинация столбцов является ключом (так что группировка семантически не нужна). Стоимость выполнения избыточного агрегата увеличивается из-за необходимости дважды передавать 1,4 миллиона строк через обмены хэш-разделами (операторы параллелизма с обеих сторон).
С учетом того, что соответствующие столбцы взяты из разных таблиц, передать эту уникальную информацию уникальности сложнее, чем обычно, поэтому можно избежать операции избыточной группировки и ненужных обменов.
Неэффективное распределение потоков
Как отмечено в ответе Джо Оббиша , обмен в узле 14 использует хеш-разбиение для распределения строк по потокам. К сожалению, небольшое количество строк и доступных планировщиков означает, что все три строки оказываются в одном потоке. По-видимому, параллельный план выполняется последовательно (с параллельными издержками) вплоть до обмена в узле 9.
Вы можете решить эту проблему (для получения циклического перебора или широковещательного разбиения), исключив Различную сортировку на узле 13. Самый простой способ сделать это - создать кластеризованный первичный ключ в #temp
таблице и выполнить отдельную операцию при загрузке таблицы:
CREATE TABLE #Temp
(
id integer NOT NULL PRIMARY KEY CLUSTERED
);
INSERT #Temp
(
id
)
SELECT DISTINCT
CAV.id
FROM @customAttrValIds AS CAV
WHERE
CAV.id IS NOT NULL;
Кэширование статистики временной таблицы
Несмотря на использование OPTION (RECOMPILE)
, SQL Server все еще может кэшировать объект временной таблицы и связанную с ней статистику между вызовами процедур. Как правило, это приветствуется оптимизация производительности, но если временная таблица заполняется аналогичным объемом данных о вызовах смежных процедур, перекомпилированный план может основываться на неверной статистике (кэшируется из предыдущего выполнения). Это подробно описано в моих статьях, « Временные таблицы в хранимых процедурах» и « Кэширование временных таблиц» .
Чтобы избежать этого, используйте OPTION (RECOMPILE)
вместе с явным UPDATE STATISTICS #TempTable
после заполнения временной таблицы и перед тем, как на нее будет ссылаться запрос.
Переписать запрос
В этой части предполагается, что изменения в создании #Temp
таблицы уже сделаны.
Учитывая стоимость возможных разливов хеша и избыточного агрегата (и окружающих обменов), он может заплатить за материализацию набора в узле 10:
CREATE TABLE #Temp2
(
CustomAttrID integer NOT NULL,
Assortment_Id integer NOT NULL,
);
INSERT #Temp2
(
Assortment_Id,
CustomAttrID
)
SELECT
ACAV.Assortment_Id,
CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
ON T.id = ACAV.CustomAttributeValue_Id;
ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);
PRIMARY KEY
Добавляются в отдельном шаге , чтобы обеспечить построение индекса имеет точную информацию кардинальной и избежать временных статистических таблиц кэширования вопроса.
Эта материализация, скорее всего, произойдет в памяти (избегая ввода-вывода tempdb ), если у экземпляра достаточно памяти. Это еще более вероятно после обновления до SQL Server 2012 (SP1 CU10 / SP2 CU1 или более поздней версии), который улучшил поведение Eager Write .
Это действие дает оптимизатору точную информацию о количестве элементов в промежуточном наборе, позволяет ему создавать статистику и позволяет объявить его (Assortment_Id, CustomAttrID)
в качестве ключа.
План для заполнения #Temp2
должен выглядеть следующим образом (обратите внимание, что сканирование кластеризованного индекса #Temp
не имеет четкой сортировки, и теперь обмен использует циклическое разбиение строк):
При наличии этого набора окончательный запрос становится:
SELECT
A.Id,
A.AssortmentId
FROM
(
SELECT
T.Assortment_Id
FROM #Temp2 AS T
GROUP BY
T.Assortment_Id
HAVING
COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
ON A.Id = DT.Assortment_Id
WHERE
A.AssortmentType = @asType
OPTION (RECOMPILE);
Мы могли бы вручную переписать их COUNT_BIG(DISTINCT...
как простую COUNT_BIG(*)
, но с новой ключевой информацией оптимизатор сделает это за нас:
Окончательный план может использовать соединение цикла / хеша / слияния в зависимости от статистической информации о данных, к которым у меня нет доступа. Еще одно небольшое замечание: я предположил, что такой индекс CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);
существует.
В любом случае, важная вещь в окончательных планах состоит в том, что оценки должны быть намного лучше, а сложная последовательность операций группировки была сведена к одному агрегату потока (который не требует памяти и, следовательно, не может пролиться на диск).
Трудно сказать, что производительность в этом случае на самом деле будет лучше с использованием дополнительной временной таблицы, но оценки и выбор планов будут гораздо более устойчивыми к изменениям объема и распределения данных с течением времени. Это может быть более ценным в долгосрочной перспективе, чем небольшое повышение производительности сегодня. В любом случае, теперь у вас есть гораздо больше информации, на которой можно основывать свое окончательное решение.
#temp
создание и использование будет проблемой для производительности, а не выигрышем. Вы сохраняете в неиндексированную таблицу только один раз. Попробуйте удалить его полностью (и, возможно,in (select id from #temp)
exists