Параметр Sniffing vs VARIABLES vs Recompile vs OPTIMIZE FOR UNKNOWN

40

Таким образом, у нас был длительный процесс, вызывающий проблемы этим утром (30 секунд + время выполнения). Мы решили проверить, не виноват ли сниффинг параметров. Итак, мы переписали proc и установили входящие параметры в переменные, чтобы избежать перехвата параметров. Испытанный / верный подход. Bam, время запроса улучшилось (менее 1 секунды). При просмотре плана запроса были найдены улучшения в индексе, который оригинал не использовал.

Просто чтобы убедиться, что мы не получили ложного срабатывания, мы создали dbcc freeproccache для оригинального процесса и повторно проверили, будут ли улучшенные результаты такими же. Но, к нашему удивлению, оригинальный процесс все еще работал медленно. Мы попытались снова с WITH RECOMPILE, все еще медленным (мы попытались перекомпилировать вызов в proc и внутри самого proc). Мы даже перезапустили сервер (очевидно, dev box).

Итак, мой вопрос заключается в следующем ... как может быть виноват сниффинг параметров, когда мы получаем тот же медленный запрос в пустом кеше плана ... не должно быть никаких параметров для сниффинга ???

На нас вместо этого влияют статистические данные таблицы, не связанные с кэшем плана. И если да, то почему бы установка входных параметров на переменные помогла ??

В ходе дальнейшего тестирования мы также обнаружили, что вставка OPTION (OPTIMIZE FOR UNKNOWN) во внутренние компоненты proc DID дает ожидаемый улучшенный план.

Итак, некоторые из вас, люди умнее меня, можете ли вы дать некоторые подсказки относительно того, что происходит за кулисами для получения такого типа результата?

С другой стороны, медленный план также прерывается досрочно по причине, в GoodEnoughPlanFoundто время как у быстрого плана нет причины преждевременного прерывания в фактическом плане.

В итоге

  • Создание переменных из входящих параметров (1 сек)
  • с перекомпиляцией (30+ сек)
  • dbcc freeproccache (30+ сек)
  • ВАРИАНТ (ОПТИМИЗАЦИЯ ДЛЯ UKNOWN) (1 сек)

ОБНОВИТЬ:

Смотрите план медленного выполнения здесь: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

Смотрите план быстрого выполнения здесь: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

Примечание: таблица, схема, имена объектов изменены из соображений безопасности.

RThomas
источник

Ответы:

43

Запрос

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

Таблица содержит 103 129 000 строк.

Быстрый план ищет ClientId с остаточным предикатом на дату, но ему нужно выполнить 96 поисков для получения Amount. <ParameterList>Раздел в плане заключается в следующем.

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

Медленный план выполняет поиск по дате и имеет поиск для оценки остаточного предиката на ClientId и для получения суммы (оценка 1 против фактической 7 388 383). <ParameterList>Раздел

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

Во втором случае ParameterCompiledValueэто не пустой. SQL Server успешно прослушал значения, используемые в запросе.

В книге «Практическое устранение неполадок SQL Server 2005» говорится об использовании локальных переменных.

Использование локальных переменных параметров поражения фырканья является довольно распространенным трюком, но OPTION (RECOMPILE)и OPTION (OPTIMIZE FOR)намеки ... , как правило , более элегантными и немного менее рискованные решения


Заметка

В SQL Server 2005 компиляция на уровне операторов позволяет откладывать компиляцию отдельного оператора в хранимой процедуре до первого выполнения запроса. К тому времени значение локальной переменной станет известно. Теоретически SQL Server может использовать это для анализа значений локальных переменных так же, как он анализирует параметры. Однако из-за того, что для предотвращения перехвата параметров в SQL Server 7.0 и SQL Server 2000+ обычно использовались локальные переменные, выслеживание локальных переменных не было включено в SQL Server 2005. Это может быть включено в будущем выпуске SQL Server, хотя это хорошо Причина использования одного из других вариантов, описанных в этой главе, если у вас есть выбор.


В результате быстрого тестирования, описанного выше, поведение, описанное выше, остается таким же в 2008 и 2012 годах, и переменные не отслеживаются для отложенной компиляции, а только при использовании явной OPTION RECOMPILEподсказки.

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

Несмотря на отложенную компиляцию, переменная не прослушивается, и предполагаемое количество строк является неточным

Оценки против фактических

Поэтому я предполагаю, что медленный план относится к параметризованной версии запроса.

Значение ParameterCompiledValueравно ParameterRuntimeValueдля всех параметров, поэтому это не является типичным анализом параметров (где план был составлен для одного набора значений, а затем запущен для другого набора значений).

Проблема в том, что план, составленный для правильных значений параметров, не подходит.

Вероятно, вы столкнулись с проблемой возрастания дат, описанной здесь и здесь . Для таблицы с 100 миллионами строк вам нужно вставить (или иным образом изменить) 20 миллионов, прежде чем SQL Server автоматически обновит для вас статистику. Похоже, что в прошлый раз они обновляли нулевые строки, совпадающие с диапазоном дат в запросе, но теперь это делают 7 миллионов.

Вы можете запланировать более частые обновления статистики, рассмотреть флаги трассировки 2389 - 90или использовать их, OPTIMIZE FOR UKNOWNчтобы они просто возвращались к догадкам, а не могли использовать вводящую в заблуждение статистику по datetimeстолбцу.

Это может быть необязательно в следующей версии SQL Server (после 2012 года). Связанный пункт Connect содержит интригующий ответ

Опубликовано Microsoft 28.08.2012 в 13:35
Мы сделали улучшение оценки мощности для следующего основного выпуска, которое по существу исправляет это. Следите за подробностями, как только появятся наши превью. Эрик

Это улучшение 2014 года рассматривается Бенджамином Неварезом в конце статьи:

Первый взгляд на новый оценщик мощности SQL Server .

Похоже, что новая оценка кардинальности откатится и в этом случае будет использовать среднюю плотность, а не оценку в 1 строку.

Некоторые дополнительные подробности об оценке кардинальности 2014 года и возрастающей ключевой проблеме здесь:

Новая функциональность в SQL Server 2014 - Часть 2. Новая оценка мощности

Мартин Смит
источник
29

Итак, мой вопрос заключается в следующем ... как может быть виноват анализ параметров, когда мы получаем тот же медленный запрос в пустом кэше плана ... не должно быть никаких параметров для анализа?

Когда SQL Server компилирует запрос, содержащий значения параметров, он анализирует конкретные значения этих параметров для оценки количества элементов (количества строк). В вашем случае, конкретные значения @BeginDate, @EndDateи @ClientIDиспользуются при выборе плана выполнения. Вы можете найти более подробную информацию о параметризации здесь и здесь . Я предоставляю эти справочные ссылки, потому что вопрос, приведенный выше, заставляет меня думать, что концепция в настоящее время не совсем понятна - всегда есть значения параметров, которые необходимо прослушать при компиляции плана.

Во всяком случае, это все не относится к делу, потому что перехват параметров здесь не проблема, как указал Мартин Смит. В то время, когда медленный запрос был скомпилирован, статистика показала, что для вынюхиваемых значений @BeginDateи не было строк @EndDate:

Медленный план вынюхивал значения

Измеренные значения появились совсем недавно, что указывает на возрастающую ключевую проблему, о которой упоминает Мартин. Поскольку поиск индекса по датам, по оценкам, возвращает только одну строку, оптимизатор выбирает план, который помещает предикат ClientIDв оператор поиска ключей в качестве остатка.

Оценка в одной строке также является причиной, по которой оптимизатор прекращает поиск более эффективных планов, возвращая сообщение «Достаточно хорошо найден план». Предполагаемая общая стоимость медленного плана с оценкой в ​​одну строку составляет всего 0,013136 единиц стоимости, поэтому нет смысла пытаться найти что-то лучшее. За исключением, конечно, поиск на самом деле возвращает 7 388 383 строки, а не одну, вызывая такое же количество поисков по ключевым словам.

Статистику может быть сложно поддерживать в актуальном состоянии и использовать для больших таблиц, а разделение создает собственные проблемы в этом отношении. Я не имел особого успеха с флагами трассировки 2389 и 2390, но вы можете их протестировать. В более поздних сборках SQL Server (R2 SP1 и более поздних версий) доступны динамические обновления статистики , но эти обновления статистики для каждого раздела все еще не реализованы. Тем временем вы можете запланировать обновление статистики вручную, когда вносите значительные изменения в эту таблицу.

Для этого конкретного запроса я бы подумал о реализации индекса, предложенного оптимизатором во время составления плана быстрого запроса:

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

Индекс должен быть выровнен по разделам с ON PartitionSchemeName (PostedDate)предложением, но суть в том, что предоставление очевидно лучшего пути доступа к данным поможет оптимизатору избежать неправильного выбора плана, не прибегая к OPTIMIZE FOR UNKNOWNподсказкам или старомодным обходным путям, таким как использование локальных переменных.

Благодаря улучшенному индексу поиск ключевых слов для извлечения Amountстолбца будет исключен, обработчик запросов все еще может выполнять динамическое удаление разделов и использовать поиск для поиска конкретного ClientIDдиапазона и диапазона дат.

Пол Уайт говорит, что GoFundMonica
источник
Хотел бы я отметить два ответа как правильные, но опять же, спасибо за дополнительную информацию - очень поучительно.
RThomas
1
Прошло пару лет с тех пор, как я опубликовал это ... но я просто хотел, чтобы вы знали. Я до сих пор использую термин «не совсем понятный», черт возьми, и всегда думаю о Поле Уайте, когда это понимаю. Заставляет меня смеяться каждый раз.
RThomas
0

У меня была точно такая же проблема , когда хранимая процедура стала медленно, а OPTIMIZE FOR UNKNOWNи RECOMPILEподсказки запросов разрешило медлительность и ускорили время выполнения. Однако следующие два метода не повлияли на медлительность хранимой процедуры: (i) очистка кэша (ii) с использованием WITH RECOMPILE. Так что, как вы и сказали, на самом деле это не было анализом параметров.

Флаги трассировки 2389 и 2390 также не помогли. Просто обновление статистики ( EXEC sp_updatestats) сделало это для меня.

Aali
источник