Почему второе INSERT
утверждение примерно в 5 раз медленнее первого?
Исходя из объема сгенерированных данных журнала, я думаю, что второе не соответствует минимальному уровню ведения журнала. Тем не менее, документация в Руководстве по производительности при загрузке данных указывает на то, что обе вставки должны быть минимально зарегистрированы. Итак, если минимальное ведение журнала является ключевым отличием производительности, почему второй запрос не соответствует минимальному ведению журнала? Что можно сделать, чтобы улучшить ситуацию?
Запрос № 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
Полный сценарий
Смотрите этот Pastebin для полного набора сценариев для генерации тестовых данных и выполнения любого из этих сценариев. Обратите внимание, что вы должны использовать базу данных, которая находится в SIMPLE
модели восстановления .
Бизнес-контекст
Мы редко перемещаемся по миллионам строк данных, и важно, чтобы эти операции были максимально эффективными как с точки зрения времени выполнения, так и с точки зрения загрузки дискового ввода-вывода. Изначально у нас сложилось впечатление, что создание таблицы кучи и ее использование INSERT...WITH (TABLOCK)
- хороший способ сделать это, но теперь мы стали менее уверенными, учитывая, что мы наблюдали ситуацию, продемонстрированную выше в реальном производственном сценарии (хотя с более сложными запросами, а не с упрощенная версия здесь).
источник
SELECT
оператор с многочисленными объединениями, который генерирует набор результатов дляINSERT
. Эти объединения дают плохие оценки количества элементов для оператора вставки окончательной таблицы (который я смоделировал в сценарии repro с помощью неверногоUPDATE STATISTICS
вызова), и поэтому это не так просто, как вводUPDATE STATISTICS
команды для устранения проблемы. Я полностью согласен с тем, что упрощение запроса, чтобы его было легче понять оценщику мощности, может быть хорошим подходом, но оно не является тривиальным для реализации данной сложной бизнес-логики.Продолжая идею Пола, можно обойти эту проблему, если вы действительно отчаянно нуждаетесь, - добавить фиктивную таблицу, которая гарантирует, что предполагаемое количество строк для вставки будет достаточно высоким для обеспечения качества при массовой загрузке. Я подтвердил, что это обеспечивает минимальное ведение журнала и повышает производительность запросов.
Финальные выносы
SELECT...INTO
для одноразовых операций вставки, если требуется минимальное ведение журнала. Как указывает Пол, это обеспечит минимальное ведение журнала независимо от оценки строкиСвязанная статья
В блоге Пола Уайта за май 2019 года « Минимальное ведение журнала с помощью команды INSERT… SELECT в таблицы кучи» более подробно описывается эта информация.
источник