Чи корисний оператор зворотної котушки для цього видалення з кластерного стовпчика?


28

Я тестую видалення даних із кластерного індексу стовпців стовпців.

Я помітив, що в плані виконання є великий нетерплячий оператор котушки:

введіть тут опис зображення

Це доповнює наступні характеристики:

  • Видалено 60 мільйонів рядків
  • 1,9 GiB використовується TempDB
  • Час виконання 14 хвилин
  • Серійний план
  • 1 перемотка на котушку
  • Орієнтовна вартість сканування: 364.821

Якщо я обману оцінювача недооцінювати, я отримую більш швидкий план, який дозволяє уникнути використання TempDB:

введіть тут опис зображення

Орієнтовна вартість сканування: 56.901

(Це приблизний план, але цифри в коментарях є правильними.)

Цікаво, що золотник знову зникає, якщо я промиваю дельта-сховища, виконавши наступне:

ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

Здається, що котушка буде введена лише тоді, коли в магазинах дельти є більше ніж поріг сторінок.

Щоб перевірити розмір дельта-магазинів, я запускаю наступний запит, щоб перевірити наявність рядкових сторінок для таблиці:

SELECT  
  SUM([in_row_used_page_count]) AS in_row_used_pages,
  SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');

Чи є якась правдоподібна користь для ітератора котушки у першому плані? Я повинен припустити, що він призначений як підвищення продуктивності, а не для захисту від Хеллоуїна, оскільки його присутність не є послідовною.

Я тестую це на 2016 CTP 3.1, але я бачу таку ж поведінку в 2014 SP1 CU3.

Я опублікував сценарій, який генерує схему та дані, і провідить вас через демонстрацію проблеми тут .

Питання здебільшого викликає цікавість щодо поведінки оптимізатора в даний момент, оскільки у мене є вирішення проблеми, яка спонукала це питання (велика котушка заповнена TempDB). Тепер я видаляю, використовуючи замість цього перемикання розділів.


2
Якщо я спробую, OPTION (QUERYRULEOFF EnforceHPandAccCard)золотник зникає. Я припускаю, що HP може бути "Захистом на Хеллоуїн". Однак потім спроба використати цей план із USE PLANпідказкою не вдається (як і спроба використовувати план з OPTIMIZE FOR обходу)
Мартін Сміт

Дякую @MartinSmith. Будь-яка ідея, що AccCardбуло б? Можливо, кардинальність висхідної колонки?
Джеймс Л

1
@JamesLupolt Ні, я не міг придумати нічого особливо переконливого для мене. Можливо, Acc - це накопичення чи доступ?
Мартін Сміт

Відповіді:


22

Чи є якась правдоподібна користь для ітератора котушки у першому плані?

Це залежить від того, що ви вважаєте "правдоподібним", але відповідь відповідно до вартісної моделі - так. Звичайно, це правда, адже оптимізатор завжди вибирає найдешевший план, який він знайде.

Справжнє питання полягає в тому, чому модель витрат вважає план із котушкою набагато дешевшим, ніж план без. Розгляньте передбачувані плани, створені для нової таблиці (з вашого сценарію), перш ніж будь-які рядки будуть додані в магазин delta:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE);

Орієнтовна вартість цього плану становить величезні 771 734 одиниці :

Оригінальний план

Вартість майже всіх пов'язана з кластерним індексом видалення, оскільки очікується, що видалення призведе до великої кількості випадкових вводу-виводу. Це просто загальна логіка, яка стосується всіх модифікацій даних. Наприклад, невпорядкований набір модифікацій індексу b-дерева передбачається в основному випадковим входом / виводом з пов'язаною з цим високою вартістю вводу / виводу.

Плани, що змінюють дані, можуть містити сортування за поданням рядків у порядку, який сприятиме послідовному доступу, саме з цих причин витрат. Вплив посилюється в цьому випадку, оскільки таблиця розділена. Насправді дуже розділений; ваш сценарій створює 15 000 з них. Випадкові оновлення дуже розділеної таблиці коштують особливо високо, оскільки ціна на перемикання розділів (наборів рядків) середнього потоку також має високу вартість.

Останнім головним фактором, який слід врахувати, є те, що простий запит на оновлення вище (де "оновлення" означає будь-яку операцію зі зміни даних, включаючи видалення) відповідає оптимізації під назвою "спільний набір рядків", де той самий внутрішній набір рядків використовується як для сканування, так і для оновлення таблиці. План виконання все ще показує два окремих оператора, але, тим не менш, використовується лише один набір рядків.

Я згадую про це, оскільки мати можливість застосувати цю оптимізацію означає, що оптимізатор проходить шлях коду, який просто не враховує потенційні переваги явного сортування, щоб зменшити витрати на випадкові введення / виведення. Там, де таблиця є b-деревом, це має сенс, оскільки структура впорядкована, тому спільний набір рядків автоматично забезпечує всі потенційні переваги.

Важливим наслідком є ​​те, що логіка витрат для оператора оновлення не враховує цю перевагу впорядкування (сприяння послідовному вводу / виводу або інші оптимізації), де основним об'єктом є зберігання стовпців. Це відбувається тому, що зміни магазину стовпців не виконуються на місці; вони використовують магазин дельти. Таким чином, модель витрат відображає різницю між оновленнями набору спільних рядів на b-деревах порівняно з стовпчиками.

Тим не менш, у спеціальному випадку (дуже!) Розділеного стовпчика стовпців все ж може бути користь збереженому впорядкуванню, оскільки виконання всіх оновлень одного розділу перед переходом до наступного може все-таки бути вигідним з точки зору вводу / виводу. .

Логіка стандартної вартості використовується повторно для магазинів стовпців, тому план, який зберігає впорядкування розділів (хоча і не порядок у кожному розділі), витрачається нижче. Це ми можемо побачити на тестовому запиті, використовуючи недокументований прапор 2332 простеження, щоб вимагати відсортованого введення оператору оновлення. Це встановлює DMLRequestSortвластивість true у процесі оновлення та змушує оптимізатор створити план, який забезпечує всі рядки для одного розділу перед тим, як перейти до наступного:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332);

Орієнтовна вартість цього плану є значно нижчою, 52,5174 одиниці:

DMLRequestSort = вірний план

Це зниження вартості пов'язане з меншою оцінною вартістю вводу / виводу при оновленні. Введений Spool не виконує жодної корисної функції, за винятком того, що він може гарантувати вихід у порядку розділів, як цього вимагає оновлення DMLRequestSort = true(послідовне сканування індексу зберігання стовпців не може забезпечити цю гарантію). Вартість самої котушки вважається відносно низькою, особливо порівняно з (ймовірно, нереальним) зниженням витрат на оновлення.

Рішення про необхідність впорядкованого введення оператору оновлення приймається дуже рано в оптимізації запитів. Евристика, використана в цьому рішенні, ніколи не була задокументована, але може бути визначена шляхом спроб та помилок. Здається, що розмір будь-яких магазинів дельти є вкладом у це рішення. Щойно зроблено, вибір є постійним для складання запитів. Жоден USE PLANнатяк не вдасться: ціль плану або замовила введення в оновлення, або його немає.

Є ще один спосіб отримати недорогий план цього запиту, не штучно обмежуючи оцінку кардинальності. Досить низька оцінка, щоб уникнути отримання катушки, ймовірно, призведе до помилки DMLRequestSort, що призведе до дуже високої оціночної вартості плану через очікувані випадкові введення / виведення. Альтернативою є використання прапора трассировки 8649 (паралельний план) спільно з 2332 (DMLRequestSort = true):

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332, QUERYTRACEON 8649);

Це призводить до плану, який використовує паралельне сканування пакетного режиму на розділ та обмін Gather Streams для збереження порядку (об'єднання):

Замовлено видалити

Залежно від ефективності виконання замовлення розділів на вашому обладнання, це може бути найкращим із трьох. Однак, великі модифікації не є чудовою ідеєю щодо зберігання стовпців, тому ідея перемикання розділів майже напевно краща. Якщо ви можете впоратися з довгими часом компіляції та вигадливим вибором плану, що часто спостерігається з об’єктами, що розділяються, особливо коли кількість розділів велика.

Поєднання багатьох, відносно нових, особливостей, особливо поблизу їхніх меж, є чудовим способом отримати погані плани виконання. Глибина підтримки оптимізатора, як правило, з часом покращується, але використання 15000 розділів магазину стовпців, ймовірно, завжди означає, що ви живете в цікаві часи.

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