План був складений на екземплярі RTM SQL Server 2008 R2 RT (збірка 10.50.1600). Вам слід встановити Service Pack 3 (збірка 10.50.6000) з подальшими останніми виправленнями, щоб довести його до (поточної) останньої збірки 10.50.6542. Це важливо з кількох причин, включаючи безпеку, виправлення помилок та нові функції.
Оптимізація вбудовування параметрів
Відповідно до цього питання, SQL Server 2008 R2 RTM не підтримує оптимізацію вбудовування параметрів (PEO) для OPTION (RECOMPILE)
. Зараз ви оплачуєте витрати на перекомпіляції, не усвідомлюючи однієї з головних переваг.
Коли PEO доступний, SQL Server може використовувати буквальні значення, що зберігаються в локальних змінних та параметрах безпосередньо в плані запитів. Це може призвести до кардинальних спрощень та підвищення продуктивності. Про це можна отримати більше інформації в моїй статті « Параметри нюху», «Вбудовування» та «РЕКОМПЛІЙ» .
Хеш, сортування та обмін розливів
Вони відображаються в планах виконання лише тоді, коли запит був складений на SQL Server 2012 або новіших версіях. У попередніх версіях нам довелося стежити за розлиттями, поки запит виконувався за допомогою Profiler або Extended Events. Проливання завжди призводить до фізичного вводу / виводу до (і від) стійкого tempdb резервного зберігання , що може мати важливі наслідки для продуктивності, особливо якщо кількість розливу є великим або шлях вводу / виводу знаходиться під тиском.
У вашому плані виконання є два оператори Hash Match (Aggregate). Пам'ять, зарезервована для хеш-таблиці, заснована на оцінці вихідних рядків (іншими словами, вона пропорційна кількості груп, знайдених під час виконання). Надана пам'ять фіксується безпосередньо перед початком виконання і не може зростати під час виконання, незалежно від того, скільки вільної пам'яті має примірник. У наданому плані обидва оператори Hash Match (Aggregate) видають більше рядків, ніж очікував оптимізатор, і тому може виникнути розлив tempdb під час виконання.
У плані також є оператор Hash Match (Inner Join). Пам'ять, зарезервована для хеш-таблиці, заснована на оцінці для вхідних рядків на боці . Вхід зонда оцінює 847 399 рядків, але під час виконання зустрічаються 1223 636. Цей надлишок також може стати причиною розливу хешу.
Надлишковий агрегат
Hash Match (агрегат) у вузлі 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 I / O), якщо екземпляр має достатньо пам'яті. Це ще ймовірніше, коли ви оновите до 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
підзапит.