Улучшение INSERT INTO - FROM SELECT, SQL-запрос - PullRequest
6 голосов
/ 12 февраля 2009

В настоящее время я получил этот тип запроса, сгенерированного программированием (c #)

INSERT INTO TableName (Field1, Field2, Field3)
SELECT Field1, Field2, Field3 FROM TableName2

Проблема в том, что SELECT может иметь результат множества записей (например, миллион), поэтому это занимает много раз, а результатом является тайм-аут соединения.

Кроме того, если я разделю все вставки на одну вставку (для этого примера - один миллион запросов на вставку), выполнение займет очень много времени ... но это работает ...

Есть ли способ улучшить этот тип запроса?

Я использую MSSQl 2005

Thx

Ответы [ 10 ]

8 голосов
/ 12 февраля 2009

Я обнаружил, что если у вас есть много операторов INSERT, которые выполняются последовательно, вы можете повысить производительность, добавив оператор 'GO' после каждого xxxx числа операторов вставки:

...
INSERT INTO Table ( ... ) VALUES ( ... )
INSERT INTO Table ( ... ) VALUES ( ... )
INSERT INTO Table ( ... ) VALUES ( ... )
GO
INSERT INTO Table ( ... ) VALUES ( ... )
INSERT INTO Table ( ... ) VALUES ( ... )
...

Другая возможность, возможно, состоит в том, чтобы убедиться, что ваш запрос INSERT INTO .. ​​SELECT FROM не вставляет все сразу, вместо этого используйте какую-то технику подкачки:

INSERT INTO Table ...
SELECT ...
FROM OtherTable WHERE Id > x and Id < y
6 голосов
/ 12 февраля 2009

Ну, если это полная копия, мне интересно, не стоит ли вам взглянуть на инструменты массовой загрузки?

  • БОЛЬШАЯ ВСТАВКА (TSQL)
  • SqlBulkCopy (.NET)
  • bcp (командная строка)
  • и т.д.

Если бы у вас было предложение * Where, я бы проверил, что оно соответствующим образом проиндексировано ...

Дополнительно:

  • возможно сбросить индексы и триггеры перед выполнением INSERT (воссоздать позже)
  • рассмотреть возможность удаления всей таблицы и использования SELECT INTO? (см. Комментарии)
3 голосов
/ 12 февраля 2009

Хорошо, есть несколько фундаментальных проблем.

  1. I \ O - Вставка в таблицу при чтении из другой таблицы, скорее всего, приведет к конфликту дисков, если таблицы не находятся на отдельных дисках. Положите противоположные столы на физически разные шпиндели.

  2. Журнал транзакций - вам нужно убедиться, что ваш журнал транзакций находится на его собственном диске, или работать в небольших транзакциях (несколько тысяч строк одновременно), или использовать BCP \ Bulk Insert, который не регистрируется.

  3. Кластерные индексы - если вы вставляете все эти строки в целевую таблицу, а ее кластерный индекс (данные физического порядка записываются на диск) не выполняет последовательную запись, требования к вводу-выводу диска выходят за рамки, поскольку разделение страниц и перераспределение. Простым решением может быть создание кластеризованного индекса в таблице получателей, который является последовательным ключом с заполнением. Как правило, это гарантирует, что вы получите последовательные записи в таблицу и почти всегда в конце.

  4. Расширение файла - убедитесь, что у вас установлен SQL для расширения его файлов с приемлемой скоростью, например, 10% или около того. В противном случае ему придется постоянно изменять размер своих файлов и обнулять диск. Есть также способы предотвратить обнуление диска, например, включение разрешения «Массовая операция с файлами» в групповых политиках для пользователя службы Sql.

Честно говоря, кроме этого и нескольких других предложений, очень маловероятно, что вы сделаете вставку с миллионами строк в транзакции действительно быстрой. Если бы вы сделали это через Bulk Insert, это было бы значительно быстрее, хотя это могло бы быть не тем, что вам нужно с точки зрения приложения.

2 голосов
/ 12 февраля 2009

Установите CommandTimeout свойство SqlCommand, которое вы используете, на разумное значение (10 минут или около того). Помните, что CommandTimeout в секундах.

1 голос
/ 12 февраля 2009

Несколько хороших ответов здесь.

Просто добавьте, что если у вас есть индексы в таблице назначения, они замедляют работу. Однако перестройка индекса иногда может занять много времени, если вы применяете метод drop create.

Если вы не хотите отбрасывать индексы, используйте ORDER BY в вашем SELECT, который соответствует кластерному индексу destination , это, кажется, помогает (вероятно, помогает минимизировать разбиение страницы).

0 голосов
/ 12 февраля 2009

Тестировали ли вы sql через sql studio studio management, чтобы увидеть, сколько времени это на самом деле занимает? Я бы начал там. Вы можете улучшить производительность выбора. И вы могли бы улучшить производительность с помощью подсказок на столе, в который вы вставляете.

0 голосов
/ 12 февраля 2009

Другой способ, который мы использовали в прошлом, - это создать временную таблицу с первичными ключами, которые мы хотим переместить и использовать в цикле while. Таким образом, вы можете сделать это в виде блочной моды, чтобы избежать больших накладных расходов транзакции, если вы отменили ее и пришлось откатиться.

В основном то, что вы в итоге делаете, это вставка в имя таблицы (...) выбор (...) из имени таблицы, в которой находится первичный ключ (выберите топ 10000 ключ из временного)

верхние 10000, которые вы хотите во вторичном наборе результатов, чтобы вы могли удалить их из временной таблицы, чтобы они больше не обрабатывались.

Еще одним способом было бы использование курсоров для уменьшения количества записей, которые вы обрабатываете за раз.

Другой метод цикла - сделать что-то подобное в цикле while.

объявить @stop как int set @stop = (выберите count (primaryKey) из tableName, где primaryKey отсутствует в файле destinstiontable)

while (@stop> 0) начать транзакцию

вставить в destinationTable (...) выберите (...) из исходной таблицы, где не указан primaryKey (выберите основной ключ из назначения)

совершить

set @stop = (выберите count (primaryKey) из tableName, где primaryKey отсутствует в destinstiontable) конец

Не самый эффективный, но он сработает и должен позволить вам вести журнал транзакций. Если вам не нужно это, обязательно используйте ключевое слово no lock, чтобы не блокировать другие транзакции при выполнении этого большого перемещения (если только вы не используете BCP или DTS, так как они намного быстрее).

Хотя, кое-что из сказанного, вероятно, является вашей лучшей ставкой. Используйте BCP, DTS или другой инструмент для массовых операций. Если вы можете удалить индексы, это заставит дела идти намного быстрее.

0 голосов
/ 12 февраля 2009

Во-первых, никогда не пытайтесь вставить миллион записей через C #. Никогда не обрабатывайте большие группы записей по одной. Это работа, которую должна выполнять база данных. Используйте для этого массовую вставку, SSIS или DTS. А затем запланируйте это как работу в нерабочее время. Если это все еще занимает слишком много времени, то я предлагаю вам запустить его партиями по несколько тысяч (вам придется поиграть с собственной базой данных, чтобы увидеть, какой вариант лучше всего подходит, поскольку число, которое вы можете безопасно обрабатывать, сильно зависит от таблиц, от того, как быстро работает ваш сервер и сколько пользователей пытаются работать с теми же таблицами.

0 голосов
/ 12 февраля 2009

либо массовую загрузку с использованием файла, а затем вставьте bcp / BULK INSERT, либо группируйте его партиями по 5 КБ или около того

0 голосов
/ 12 февраля 2009

Вы не указываете, какую проблему вы решаете с помощью этого подхода. Очевидно, что ГДЕ сузит набор рекордов. Но если набор результатов не будет изменен в новой таблице, то зачем вообще копировать данные? Почему бы не сделать запрос напрямую из источника?

...