Я провожу сравнение производительности при использовании 1000 операторов INSERT:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('6f3f7257-a3d8-4a78-b2e1-c9b767cfe1c1', 'First 0', 'Last 0', 0)
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('32023304-2e55-4768-8e52-1ba589b82c8b', 'First 1', 'Last 1', 1)
...
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('f34d95a7-90b1-4558-be10-6ceacd53e4c4', 'First 999', 'Last 999', 999)
..versus с использованием одного оператора INSERT с 1000 значениями:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES
('db72b358-e9b5-4101-8d11-7d7ea3a0ae7d', 'First 0', 'Last 0', 0),
('6a4874ab-b6a3-4aa4-8ed4-a167ab21dd3d', 'First 1', 'Last 1', 1),
...
('9d7f2a58-7e57-4ed4-ba54-5e9e335fb56c', 'First 999', 'Last 999', 999)
К моему большому удивлению, результаты оказались противоположными тому, что я думал:
- 1000 операторов INSERT: 290 мсек.
- 1 оператор INSERT с 1000 VALUES: 2800 мсек.
Тест выполняется непосредственно в MSSQL Management Studio с использованием SQL Server Profiler для измерения (и я получил аналогичные результаты, запустив его из кода C # с использованием SqlClient, что еще более удивительно, учитывая все циклы обхода слоев DAL)
Можно ли это разумно или как-то объяснить? Как же, якобы быстрее результаты метода в 10 раз (!) Хуже производительность?
Спасибо.
РЕДАКТИРОВАТЬ: прикрепление планов выполнения для обоих:
Ответы:
Ваш план показывает, что отдельные вставки используют параметризованные процедуры (возможно, автоматически параметризованные), поэтому время их синтаксического анализа / компиляции должно быть минимальным.
Я подумал, что займусь этим немного подробнее, поэтому настройте цикл ( скрипт ) и попытался количество
VALUES
предложений и записать время компиляции.Затем я разделил время компиляции на количество строк, чтобы получить среднее время компиляции для каждого предложения. Результаты ниже
Вплоть до 250
VALUES
предложений время компиляции / количество предложений имеет небольшую тенденцию к увеличению, но не слишком драматичную.Но затем происходит внезапное изменение.
Этот раздел данных показан ниже.
Размер кэшированного плана, который увеличивался линейно, внезапно падает, но CompileTime увеличивается в 7 раз, а CompileMemory - вверх. Это точка отсечения между автоматически параметризованным планом (с 1000 параметрами) и непараметризованным планом. После этого он, кажется, становится линейно менее эффективным (с точки зрения количества предложений значений, обрабатываемых за заданное время).
Не уверен, почему это должно быть. Предположительно, когда он составляет план для конкретных буквальных значений, он должен выполнять некоторые действия, которые не масштабируются линейно (например, сортировка).
Похоже, что это не влияет на размер кешированного плана запроса, когда я пробовал запрос, состоящий полностью из повторяющихся строк, и не влияет на порядок вывода таблицы констант (и когда вы вставляете в кучу время, потраченное на сортировку в любом случае было бы бессмысленно, даже если бы это было).
Более того, если в таблицу добавляется кластеризованный индекс, план по-прежнему показывает явный шаг сортировки, поэтому во время компиляции не выполняется сортировка, чтобы избежать сортировки во время выполнения.
Я попытался посмотреть на это в отладчике, но общедоступные символы для моей версии SQL Server 2008, похоже, недоступны, поэтому вместо этого мне пришлось посмотреть на эквивалент
UNION ALL
конструкцию в SQL Server 2005.Типичная трассировка стека ниже
Таким образом, если отбросить имена в трассировке стека, кажется, что на сравнение строк уходит много времени.
В этой статье базы знаний указано, что
DeriveNormalizedGroupProperties
это связано с тем, что раньше называлось нормализацией. этапом обработки запросов.Этот этап теперь называется привязкой или алгебраизацией, и он берет выходные данные дерева синтаксического анализа выражений с предыдущего этапа синтаксического анализа и выводит дерево алгебризованных выражений (дерево обработчика запросов) для перехода к оптимизации (в данном случае - тривиальной оптимизации плана) [ref] .
Я попробовал еще один эксперимент ( сценарий ), который заключался в повторном запуске исходного теста, но с рассмотрением трех разных случаев.
Хорошо видно, что чем длиннее струны, тем хуже получается, и, наоборот, чем больше дубликатов, тем лучше. Как упоминалось ранее, дубликаты не влияют на размер кэшированного плана, поэтому я предполагаю, что при построении самого алгебризованного дерева выражений должен существовать процесс идентификации дубликатов.
редактировать
Одно место, где используется эта информация, показано здесь @Lieven.
Поскольку во время компиляции он может определить, что
Name
столбец не имеет дубликатов, он пропускает упорядочение по вторичному1/ (ID - ID)
выражению во время выполнения (сортировка в плане имеет только одинORDER BY
столбец), и ошибка деления на ноль не возникает. Если в таблицу добавляются дубликаты, тогда оператор сортировки показывает два столбца в порядке столбцов, и возникает ожидаемая ошибка.источник
<ParameterList>
чем один с<ConstantScan><Values><Row>
списком.<ConstantScan><Values><Row>
вместо<ParameterList>
.Это неудивительно: план выполнения крошечной вставки вычисляется один раз, а затем повторно используется 1000 раз. Анализ и подготовка плана выполняются быстро, потому что у него всего четыре значения, с которыми нужно работать. План с 1000 строками, с другой стороны, должен иметь дело с 4000 значений (или 4000 параметров, если вы параметризовали свои тесты C #). Это может легко съесть экономию времени, которую вы получите, исключив 999 обратных обращений к SQL Server, особенно если ваша сеть не слишком медленная.
источник
Вероятно, проблема связана со временем, необходимым для компиляции запроса.
Если вы хотите ускорить вставки, вам действительно нужно заключить их в транзакцию:
В C # вы также можете рассмотреть возможность использования параметра со значением таблицы. Выдача нескольких команд в одном пакете путем разделения их точкой с запятой - еще один подход, который также может помочь.
источник
Я столкнулся с аналогичной ситуацией, пытаясь преобразовать таблицу с несколькими строками по 100 тыс. С помощью программы на C ++ (MFC / ODBC).
Поскольку эта операция заняла очень много времени, я решил объединить несколько вставок в одну (до 1000 из-за ограничений MSSQL ). Я предполагаю, что множество отдельных операторов вставки вызовут накладные расходы, аналогичные описанным здесь .
Однако оказывается, что преобразование заняло немного больше времени:
Таким образом, 1000 отдельных вызовов CDatabase :: ExecuteSql, каждый с одним оператором INSERT (метод 1), примерно в два раза быстрее, чем один вызов CDatabase :: ExecuteSql с многострочным оператором INSERT с 1000 кортежами значений (метод 2).
Обновление: Итак, следующее, что я попробовал, - это объединить 1000 отдельных операторов INSERT в одну строку и заставить сервер выполнить это (метод 3). Оказывается, это даже немного быстрее, чем метод 1.
Изменить: я использую Microsoft SQL Server Express Edition (64-разрядная версия) v10.0.2531.0
источник