SQL Server завжди використовує комбінацію операторів розділення, сортування та згортання при підтримці унікального індексу як частини оновлення, яке впливає (або може вплинути) на більш ніж один рядок.
Опрацьовуючи приклад у запитанні, ми можемо записати оновлення як окреме однорядне оновлення для кожного з чотирьох присутніх рядків:
-- Per row updates
UPDATE dbo.Banana SET pk = 2 WHERE pk = 1;
UPDATE dbo.Banana SET pk = 3 WHERE pk = 2;
UPDATE dbo.Banana SET pk = 4 WHERE pk = 3;
UPDATE dbo.Banana SET pk = 5 WHERE pk = 4;
Проблема полягає в тому, що перше твердження буде невдалим, оскільки воно змінюється pk
з 1 на 2, і вже є рядок, деpk
= 2. Система зберігання SQL Server вимагає, щоб унікальні індекси залишалися унікальними на кожному етапі обробки, навіть у межах одного оператора . Це проблема, яку вирішили розділити, сортувати та згортати.
Розкол
Перший крок - розділити кожну операцію оновлення на видалення, а потім вставити:
DELETE dbo.Banana WHERE pk = 1;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Оператор Split додає стовпчик коду дії до потоку (тут позначений Act1007):
Код дії - 1 для оновлення, 3 для видалення та 4 для вставки.
Сортувати
Розподілене твердження вище все ще призведе до помилкового перехідного унікального порушення ключа, тому наступним кроком є сортування висловлювань за ключами унікального індексу, що оновлюється ( pk
у цьому випадку), а потім за кодом дії. У цьому прикладі це просто означає, що видалення (3) на одному ключі впорядковується перед вставками (4). Отримане замовлення:
-- Sort (pk, action)
DELETE dbo.Banana WHERE pk = 1;
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Згорнути
Попереднього етапу достатньо для гарантування уникнення помилкових порушень унікальності у всіх випадках. В якості оптимізації Collapse поєднує в оновлення суміжні вилучення та вставки одного і того ж ключового значення :
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1;
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2;
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3;
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Пари видалення / вставки для pk
значень 2, 3 і 4 об'єднали в оновлення, залишивши одне видалення на pk
= 1, а вставку для pk
= 5.
Оператор Collapse згрупує рядки за ключовими стовпцями та оновлює код дії для відображення результату згортання:
Кластерне оновлення індексу
Цей оператор позначений як Оновлення, але він може вставляти, оновлювати та видаляти. Які дії виконуються за допомогою оновлення індексу кластера для кожного рядка, визначається значенням коду дії в цьому рядку. Оператор має властивість Action для відображення цього режиму роботи:
Рядки модифікації лічильників
Зауважте, що три вищенаведені оновлення не змінюють ключ (и) унікального індексу, що підтримується. Насправді ми перетворили оновлення ключових стовпців в індексі в оновлення неклавішних стовпців ( c1
і c2
), плюс видалення та вставки. Ні видалення, ні вставка не можуть спричинити помилкове порушення унікального ключа.
Вставлення чи видалення впливає на кожен окремий стовпець у рядку, тому статистика, пов’язана з кожним стовпцем, збільшуватиме свої лічильники модифікацій. Для оновлень (ів) лише статистика з будь-яким із оновлених стовпців як провідний стовпець збільшує свої лічильники модифікацій (навіть якщо значення не змінюється).
Отже, лічильники модифікацій рядків рядків показують 2 зміни pk
та 5 для c1
та c2
:
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1; -- All columns modified
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4; -- c1 and c2 modified
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z'); -- All columns modified
Примітка: Тільки зміни, застосовані до базового об'єкта (купи або кластерний індекс), впливають на лічильники модифікації рядків рядків статистики. Некластеризовані індекси є вторинними структурами, що відображають зміни, вже внесені до базового об'єкта. Вони взагалі не впливають на лічильники модифікації рядків статистики.
Якщо об’єкт має кілька унікальних індексів, для організації оновлень до кожного використовується окрема комбінація розділення, сортування, згортання. SQL Server оптимізує цей випадок для некластеризованих індексів, зберігаючи результат розділення на котушку Eager Table, а потім відтворюючи цей набір для кожного унікального індексу (який буде мати власний сортування за індексними ключами + код дії та згортання).
Вплив на оновлення статистики
Автоматичні оновлення статистики (якщо вони включені) відбуваються, коли оптимізатору запитів потрібна статистична інформація та помічає, що наявна статистика застаріла (або недійсна через зміну схеми). Статистика вважається застарілою, коли кількість зафіксованих модифікацій перевищує поріг.
Упорядкування розділення / сортування / згортання призводить до того, що різні зміни рядків записуються, ніж можна було б очікувати. Це, у свою чергу, означає, що оновлення статистики може бути запущено рано чи пізно, ніж це було б інакше.
У наведеному вище прикладі зміни рядків для стовпчика ключів збільшуються на 2 (зміна чистої), а не на 4 (по одній для кожного порушеного рядка таблиці) або на 5 (по одній для кожного видалення / оновлення / вставки, виробленого згортанням).
Крім того, неклавішні стовпці, які логічно не були змінені оригінальним запитом, накопичують модифікації рядків, які можуть налічувати аж двічі оновлених рядків таблиці (один для кожного видалення та один для кожної вставки).
Кількість зафіксованих змін залежить від ступеня перекриття між старими та новими значеннями стовпців ключових (і тому ступінь, на яку можна згортати окремі видалення та вставки). Скидання таблиці між кожним виконанням, наступні запити демонструють вплив на лічильники модифікації рядків з різними перекриттями:
UPDATE dbo.Banana SET pk = pk + 0; -- Full overlap
UPDATE dbo.Banana SET pk = pk + 1;
UPDATE dbo.Banana SET pk = pk + 2;
UPDATE dbo.Banana SET pk = pk + 3;
UPDATE dbo.Banana SET pk = pk + 4; -- No overlap