У меня есть SQL-запрос, который я провел последние два дня, пытаясь оптимизировать, используя метод проб и ошибок и план выполнения, но безрезультатно. Пожалуйста, прости меня за это, но я опубликую весь план выполнения здесь. Я приложил усилия, чтобы сделать имена таблиц и столбцов в запросе и плане выполнения общими для краткости и для защиты IP моей компании. План выполнения можно открыть с помощью SQL Sentry Plan Explorer .
Я сделал довольно много T-SQL, но использование планов выполнения для оптимизации моего запроса - это новая область для меня, и я действительно пытался понять, как это сделать. Поэтому, если бы кто-нибудь мог помочь мне с этим и объяснить, как этот план выполнения может быть расшифрован, чтобы найти в запросе способы его оптимизации, я был бы вечно благодарен. У меня есть еще много запросов для оптимизации - мне просто нужен трамплин, чтобы помочь мне с этим первым.
Это запрос:
DECLARE @Param0 DATETIME = '2013-07-29';
DECLARE @Param1 INT = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50) = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50) = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50) = 'Text3';
SET NOCOUNT ON
DECLARE @MyTableVar TABLE
(
B_Var1_PK int,
Job_Var1 varchar(512),
Job_Var2 varchar(50)
)
INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2)
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);
CREATE TABLE #TempTable
(
TTVar1_PK INT PRIMARY KEY,
TTVar2_LK VARCHAR(100),
TTVar3_LK VARCHAR(50),
TTVar4_LK INT,
TTVar5 VARCHAR(20)
);
INSERT INTO #TempTable
SELECT DISTINCT
T.T1_PK,
T.T1_Var1_LK,
T.T1_Var2_LK,
MAX(T.T1_Var3_LK),
T.T1_Var4_LK
FROM
MyTable1 T
INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK
-- This is the slow statement...
SELECT
CASE E.E_Var1_LK
WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
WHEN 'Text3' THEN T.TTVar2_LK
END,
T.TTVar4_LK,
T.TTVar3_LK,
CASE E.E_Var1_LK
WHEN 'Text1' THEN F.F_Var1
WHEN 'Text2' THEN F.F_Var2
WHEN 'Text3' THEN T.TTVar5
END,
A.A_Var3_FK_LK,
C.C_Var1_PK,
SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK = T.TTVar1_PK
INNER JOIN @MyTableVar B ON B.B_Var1_PK = A.Job
INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK = A.A_Var5_FK_LK
INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK = A.A_Var6_FK_LK
INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK = A.A_Var7_FK_LK
LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
CASE E.E_Var1_LK
WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
WHEN 'Text3' THEN T.TTVar2_LK
END,
T.TTVar4_LK,
T.TTVar3_LK,
CASE E.E_Var1_LK
WHEN 'Text1' THEN F.F_Var1
WHEN 'Text2' THEN F.F_Var2
WHEN 'Text3' THEN T.TTVar5
END,
A.A_Var3_FK_LK,
C.C_Var1_PK
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
Я обнаружил, что третье утверждение (прокомментированное как медленное) - это та часть, которая занимает больше всего времени. Два заявления перед возвращением почти мгновенно.
План выполнения доступен в виде XML по этой ссылке .
Лучше щелкнуть правой кнопкой мыши и сохранить, а затем открыть в SQL Sentry Plan Explorer или в другом программном обеспечении для просмотра, а не в браузере.
Если вам нужна дополнительная информация о таблицах или данных, пожалуйста, не стесняйтесь спрашивать.
tempdb
. т. е. оценки для строк, полученных в результате соединенияTableA
и@MyTableVar
являются далеко. Кроме того, число строк, входящих в сортировку, намного больше, чем предполагалось, поэтому они также могут пролиться.Ответы:
Прежде чем перейти к основному ответу, необходимо обновить две части программного обеспечения.
Требуемые обновления программного обеспечения
Первый - это SQL Server. Вы используете SQL Server 2008 с пакетом обновления 1 (сборка 2531). Вы должны быть исправлены как минимум до текущего пакета обновления (SQL Server 2008 с пакетом обновления 3 (SP3) - сборка 5500). Самая последняя сборка SQL Server 2008 на момент написания этой статьи - Пакет обновления 3, Накопительное обновление 12 (сборка 5844).
Вторым программным обеспечением является SQL Sentry Plan Explorer . В последних версиях появились новые важные функции и исправления, в том числе возможность напрямую загружать план запросов для экспертного анализа (не нужно никуда вставлять XML!)
Анализ плана запроса
Оценка кардинальности для табличной переменной является абсолютно правильной благодаря перекомпиляции на уровне операторов:
К сожалению, переменные таблицы не поддерживают статистику распределения, поэтому оптимизатор знает только о шести строках; он ничего не знает о значениях, которые могут быть в этих шести строках. Эта информация имеет решающее значение, учитывая, что следующая операция представляет собой соединение с другой таблицей. Оценка количества элементов этого соединения основана на сомнительном предположении оптимизатора:
С этого момента план, выбранный оптимизатором, основан на неверной информации, поэтому неудивительно, что производительность настолько низкая. В частности, память, выделенная для сортировок и хеш-таблиц для хеш-соединений, будет слишком мала. Во время выполнения переполненные операции сортировки и хеширования будут распространяться на физический диск tempdb.
SQL Server 2008 не выделяет это в планах выполнения; Вы можете отслеживать разливы с помощью расширенных событий или предупреждений сортировки профилировщика и предупреждений о хеше . Память зарезервирована для сортировок и хэшей, основанных на оценках количества элементов перед запуском выполнения, и не может быть увеличена во время выполнения независимо от того, сколько резервной памяти может иметь ваш SQL Server. Поэтому точные оценки числа строк имеют решающее значение для любого плана выполнения, который включает операции, занимающие память рабочей области.
Ваш запрос также параметризован. Следует рассмотреть возможность добавления
OPTION (RECOMPILE)
к запросу, если различные значения параметров влияют на план запроса. Вероятно, вам все равно стоит рассмотреть возможность его использования, чтобы оптимизатор мог видеть значение@Param1
во время компиляции. Если ничего другого, это может помочь оптимизатору вывести более разумную оценку для поиска индекса, показанного выше, учитывая, что таблица очень большая и секционированная. Это также может включить удаление статических разделов.Попробуйте запрос снова с временной таблицей вместо табличной переменной и
OPTION (RECOMPILE)
. Вам также следует попытаться материализовать результат первого объединения в другую временную таблицу и выполнить остальную часть запроса для этого. Количество строк не так велико (3 285 620), поэтому это должно быть достаточно быстро. После этого оптимизатор будет иметь точную оценку количества элементов и статистику распределения для результата объединения. Если повезет, остальная часть плана хорошо встанет на свои места.Исходя из свойств, показанных в плане, материализационный запрос будет:
Вы также можете
INSERT
добавить в предопределенную временную таблицу (правильные типы данных не показаны в плане, поэтому я не могу выполнить эту часть). Новая временная таблица может иметь или не иметь преимущества от кластеризованных и некластеризованных индексов.источник
#AnotherTempTable
. Похоже, это оказало наилучшее влияние - другие предложения (использование временной таблицы вместо табличной переменной для @MyTableVar и использованиеOPTION (RECOMPILE)
не имели большого эффекта или вообще никакого). «Anonymize» и «Post to SQLPerformance.com» опции в SQL Sentry Plan Explorer великолепны - я только что их использовал: answers.sqlperformance.com/questions/1087Я заметил, что на @MyTableVar должен быть PK, и согласен с тем, что #MyTableVar часто лучше работает (особенно с большим количеством строк).
Условие в предложении where
должен быть перемещен во внутреннее соединение A AND. По моему опыту оптимизатор не достаточно умен, чтобы сделать это (извините, я не смотрю на план), и это может иметь огромное значение.
Если эти изменения не показывают улучшения, я бы затем создал еще одну временную таблицу из A и всех вещей, к которым он присоединяется к ограниченному (хорошо?) A.A_Var8_FK_LK = @ Param1, если эта группировка имеет логический смысл для вас.
Затем создайте кластеризованный индекс для этой временной таблицы (до или после создания) для следующего условия соединения.
Затем объедините этот результат с несколькими оставшимися таблицами (F и T).
Бэм, который нуждается в вонючем плане запросов, когда оценки строк выключены и иногда так или иначе не легко улучшить ). Я предполагаю, что у вас есть правильные показатели, которые я бы сначала проверил в плане.
След может показать разливы tempdb, которые могут иметь или не иметь сильное влияние.
Другой альтернативный подход - по крайней мере, более быстрый, чтобы попробовать - состоит в том, чтобы упорядочить таблицы от наименьшего числа строк (A) к наибольшему, а затем начать добавлять слияние, хэш и цикл к объединениям. При наличии подсказок порядок соединения фиксируется, как указано. Другие пользователи разумно избегают такого подхода, потому что он может повредить в долгосрочной перспективе, если относительное количество строк резко изменится. Желательно минимальное количество подсказок.
Если вы делаете многие из них, возможно, стоит попробовать коммерческий оптимизатор (или пробную версию), и он по-прежнему является хорошим опытом обучения.
источник