Однорядний ВСТАВКА… ВИБІРИ набагато повільніше, ніж окремий SELECT


18

Враховуючи наступну таблицю купи з 400 рядками, пронумерованими від 1 до 400:

DROP TABLE IF EXISTS dbo.N;
GO
SELECT 
    SV.number
INTO dbo.N 
FROM master.dbo.spt_values AS SV
WHERE 
    SV.[type] = N'P'
    AND SV.number BETWEEN 1 AND 400;

та наступні налаштування:

SET NOCOUNT ON;
SET STATISTICS IO, TIME OFF;
SET STATISTICS XML OFF;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

Наступне SELECTтвердження завершується приблизно за 6 секунд ( демонстрація , план ):

DECLARE @n integer = 400;

SELECT
    c = COUNT_BIG(*) 
FROM dbo.N AS N
CROSS JOIN dbo.N AS N2
CROSS JOIN dbo.N AS N3
WHERE 
    N.number <= @n
    AND N2.number <= @n
    AND N3.number <= @n
OPTION
    (OPTIMIZE FOR (@n = 1));

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

Коли однорядний вихід записується в таблицю, потрібно 19 секунд ( демонстрація , план ):

DECLARE @T table (c bigint NOT NULL);

DECLARE @n integer = 400;

INSERT @T
    (c)
SELECT
    c = COUNT_BIG(*) 
FROM dbo.N AS N
CROSS JOIN dbo.N AS N2
CROSS JOIN dbo.N AS N3
WHERE 
    N.number <= @n
    AND N2.number <= @n
    AND N3.number <= @n
OPTION
    (OPTIMIZE FOR (@n = 1));

Плани виконання виглядають однаково від вставки одного рядка.

Весь додатковий час, здається, витрачається на використання процесора.

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

Відповіді:


21

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

Миссемінація кардинальності, навмисне введена OPTIMIZE FORзасобами, що купи скануються набагато більше разів, ніж очікує оптимізатор, і він не вводить котушку, як зазвичай.

Ця комбінація факторів означає, що продуктивність дуже чутлива до кількості блокувань, необхідних під час виконання.

Оператор SELECTотримує перевагу від оптимізації, яка дозволяє пропускати загальні блоки на рівні рядків (беручи лише блокування на рівні спільних намірів на рівні сторінки), коли немає небезпеки зчитування невпорядкованих даних, а також немає даних про рядкові дані.

У цій INSERT...SELECTзаяві ця оптимізація не виграє, тому мільйони блокувань RID приймаються та вивільняються щосекунди у другому випадку, а також блокування на рівні сторінок із наміром.

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

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

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

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

Як остаточний інтерес, запит можна запустити навіть швидше, ніж оптимізований SELECTвипадок, примушуючи використовувати котушки, використовуючи прапор 8691 недокументованого сліду.

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