Sniffing параметрів vs VARIABLES vs Rekompile vs OPTIMIZE FOR NEKNOW


40

Таким чином, у нас вранці тривалий пробіг, що спричинив проблеми (30 сек + час роботи) Ми вирішили перевірити, чи не винна нюхання параметрів. Отже, ми переписали процедуру і встановили вхідні параметри змінним, щоб перемогти нюхування параметра. Перевірений / справжній підхід. Бам, час запиту покращився (менше 1 сек). При перегляді плану запитів поліпшення були знайдені в індексі, який не використовував оригінал.

Тільки для того, щоб переконатися, що ми не отримали помилковий позитив, ми зробили dbcc freeproccache на оригінальній процедурі і повторно перевірили, чи будуть покращені результати однаковими. Але, на наш подив, оригінальний пророк все ще пройшов повільно. Ми спробували ще раз з РЕКОМПЛІЮ, все ще повільно (ми спробували перекомпілювати під час виклику до прок і всередині самого прок). Ми навіть перезапустили сервер (очевидно, що Dev).

Отже, моє запитання таке ... як може бути винне нюхання параметрів, коли ми отримуємо такий же повільний запит у порожньому кеші плану ... не повинно бути жодних параметрів для нюху ???

На нас замість цього впливають статистичні дані таблиці, не пов'язані з кешем плану. І якщо так, то чому б встановити вхідні параметри змінним ??

Під час подальшого тестування ми також виявили, що вставляючи ВАРІАНТ (ОПТИМІЗУВАННЯ ДЛЯ НЕВІДОМЛЕНОГО) у внутрішніх умовах PRO 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

Примітка: назви таблиці, схеми, об'єктів змінено з міркувань безпеки.

Відповіді:


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та діапазону дат.


Бажаю, щоб я міг позначити дві відповіді як правильні, але знову ж таки, дякую за додаткову інформацію - дуже повчально.
RThomas

1
Минуло пару років, як я опублікував це ... але я просто хотів вас повідомити. Я все ще вживаю термін «недосконало зрозумілий» увесь невдалий час, і я завжди думаю про Пола Уайта, коли це роблю. Щоразу змушує насміхатися.
RThomas

0

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

Не допомогли також сліди прапор 2389 та 2390. Щойно оновлення статистики ( EXEC sp_updatestats) зробило це для мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.