Чому друге INSERT
твердження ~ 5x повільніше першого?
З огляду на кількість створених даних журналу, я думаю, що другий не відповідає мінімальному журналу. Однак документація в Посібнику з продуктивності завантаження даних вказує на те, що обидві вставки повинні бути спроможні мінімально реєструватися. Отже, якщо мінімальна реєстрація є ключовою різницею продуктивності, чому так, що другий запит не відповідає мінімальному журналу? Що можна зробити, щоб покращити ситуацію?
Запит №1: Вставка рядків 5 ММ за допомогою INSERT ... WITH (TABLOCK)
Розглянемо наступний запит, який вставляє 5 мм рядків у купу. Цей запит виконує 1 second
та генерує 64MB
дані журналу транзакцій, як повідомляється sys.dm_tran_database_transactions
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Запит №2: Вставка одних і тих же даних, але SQL занижує # рядок
Тепер розглянемо цей дуже схожий запит, який працює за точно такими ж даними, але трапляється з таблиці (або складної SELECT
заяви з багатьма об'єднаннями в моєму фактичному виробничому випадку), де оцінка кардинальності занадто низька. Цей запит виконує 5.5 seconds
та генерує 461MB
дані журналу транзакцій.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Повний сценарій
Дивіться в цій пастебі повний набір сценаріїв для генерування тестових даних та виконання будь-якого з цих сценаріїв. Зауважте, що ви повинні використовувати базу даних, яка є у SIMPLE
моделі відновлення .
Бізнес-контекст
Ми напівчасто пересуваємося мільйонами рядків даних, і важливо, щоб ці операції були максимально ефективними, як з точки зору часу виконання, так і з навантаження дискового вводу / виводу. Спочатку у нас було враження, що створення таблиці куч і використання INSERT...WITH (TABLOCK)
- це хороший спосіб зробити це, але тепер стали менш впевненими, враховуючи, що ми спостерігали ситуацію, продемонстровану вище, у фактичному виробничому сценарії (хоча і зі складнішими запитами, а не тут спрощена версія).
SELECT
заява з численними приєднаннями, яка генерує набір результатів дляINSERT
. Ці об'єднання дають погані оцінки кардинальності для оператора вставки кінцевої таблиці (який я імітував у сценарії repro через поганийUPDATE STATISTICS
виклик), і тому це не так просто, як видаватиUPDATE STATISTICS
команду для усунення проблеми. Я повністю погоджуюся, що спрощення запиту таким чином, щоб Оцінювач кардинальності легше зрозумів, може бути хорошим підходом, але реалізація заданої складної бізнес-логіки не є дрібницею.