Распараллеливание массивных вставок в SQL Server из C # (для лучшей производительности времени) - PullRequest
6 голосов
/ 16 ноября 2010

Постановка задачи: Как распараллелить вставки в SQL Server (2008)

Я выполняю масштабные числовые вычисления для научных исследований в многопоточных рабочих C #, которые в основном выполняют одно: тестирование тысячвозможные конфигурации (комбинации матриц) в течение определенного периода времени (в днях) и сохранения результатов в базе данных SQL Server.

Если я сохраняю результаты один за другим в БД (~ 300 000 строк на вычислительный сеанс * 100 сессий), один за другим, я заканчиваю тем, что жду несколько часов, чтобы завершился процесс сохранения.

Проект базы данных очень прост:

  • Комбинированные наборы
    CS_ID1, Значение A1, Значение B1, Значение C1
    CS_ID2, Значение A2, Значение B2, Значение C2
    .........

  • Результаты за день
    CS_ID1,День 1, Результат 1
    CS_ID1, День 2, Результат 2
    CS_ID1, День 3, Результат 3
    .........

    .........
    CS_ID2, день1, результат N
    CS_ID2, день2, результат N + 1
    CS_ID2, день3, результат N + 2

Каждый «Комбинированный набор» проверяется по дням выборки, а его результаты за день обрабатываются в одном потоке C #, где запрос LINQ / SQL генерируется и отправляется в БД непосредственно перед концом потока.За исключением последовательностей идентификаторов набора комбинаций, НЕТ логической связи между результатами Это очень важно: именно поэтому я подумал о распараллеливании элементов вставки как , что в основном равняется массовому дампу блоков результатов

Еще одна деталь, которая может быть важнойявляется то, что можно заранее определить, сколько строк будет вставлено в базу данных (за блок и всего).Это, вероятно, могло бы помочь организовать табличные пространства, разделить их по страницам, предварительно зафиксировать диапазоны идентификаторов, чтобы одновременно хранить блоки, или что-то в этом роде (Нет, я не "высокий" или что-то в этом роде :-))

Я приветствую любые предложения, чтобы сделать это время вставки максимально коротким.

Пожалуйста, примите во внимание, что я разработчик C #, обладаю базовыми знаниями SQL Server и не очень хорошо знаком сглубокие технические концепции DBA (я видел, что настройки блокировки ОЧЕНЬ многочисленны, что есть многопоточные и асинхронные возможности, но я должен признать, что я потерян один в лесу :-))

У меня 12 CPUДоступные ядра и 24Go RAM


РЕДАКТИРОВАТЬ: Tiebreaker
Я приветствую любые умные предложения по времени мониторинга для всего процесса: сНачало / завершение потоков C # в подробных отчетах о вставке сервера SQl (что происходит, когда, как и где).
Я пытался протоколировать с помощью NLog, но это резко смещает профессионала.Время перерыва, поэтому я ищу некоторые умные обходные пути, которые являются довольно бесшовными с минимальным воздействием.То же самое для серверной части SQL: я знаю, что есть несколько журналов и мониторинг SP.Я еще не выяснил, какие из них подходят моей ситуации.

Ответы [ 7 ]

8 голосов
/ 16 ноября 2010

300 тыс. Вставок - это считанные секунды, в худшие минуты, а не часы.Вы должны делать это неправильно.Мировой рекорд ETL SSIS в 2008 году составлял 2,36 ТБ / час, 300 000 записей - ничего .

Основные практические правила:

  • пакетный коммит .это самое главное.Не вставляйте строку, затем вставляйте строку, затем вставляйте строку в тошноту, каждая вставка занимает свою собственную транзакцию .Ваша программа должна ожидать сброса журнала (LDF) после каждого оператора в своем случае и будет медленной.Очень медленно.Вместо этого запустите транзакцию, затем вставьте пакет строк, а затем передайте транзакцию:

Псевдокод:

do
  {
  using (TransactionScope scope = new TransactionScope(
     Required, new TransactionOptions() {IsolationLevel = ReadCommitted))
  {
    for (batchsize)
    {
      ExecuteNonQuery ("Insert ...")
    }
    scope.Complete ();
  }
} while (!finished);
  • , если возможно, используйте SqlBulkCopy

Один только первый вариант даст вам более 3000 вставок в секунду (~ 2 минуты для 300k).Второй вариант должен получить десятки тысяч в секунду.Если вам нужно больше, есть более продвинутые приемы:

  • использование куч вместо b-деревьев (без кластеризованного индекса)
  • отключение вторичных индексов
  • аффинитизация клиентовМягкие узлы NUMA и вставка в заблокированные таблицы в соответствии с подключением клиента, затем переключите их все с помощью переключения разделов в конце.Это для Действительно верхнего уровня, миллионов строк в секунду.

Я предлагаю вам начать с основ основ: пакетные коммиты.

5 голосов
/ 16 ноября 2010

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

BEGIN TRANSACTION;

...<various SQL statements>...

COMMIT TRANSACTION;

Синтаксис сервера SQL см .:

http://msdn.microsoft.com/en-us/library/ms188929.aspx

http://msdn.microsoft.com/en-us/library/ms190295.aspx

По моему опыту, объединение таких вставок определенно помогает повысить производительность сервера и, в некоторой степени, использование ресурсов и сети.

EDIT:

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

Поскольку вы, очевидно, используете один компьютер для вычислений и БД, интенсивное распараллеливание транзакций БД не сильно повлияет на производительность и может даже ухудшить ситуацию, поскольку у вас нет задержек в сети, чтобы уменьшить влияние из. Пока все ядра процессора заняты, что, вероятно, подразумевает количество рабочих>> 12, вы должны смотреть на другие оптимизации.

Если ваши потоки генерируют свои выходные данные за один раз после обработки (например, если вы вычисляете большую матрицу и , то сбрасываете в базу данных) Я сомневаюсь, что вы получите что-нибудь, сохранив результат в файл и затем с БД считывает его обратно в таблицу.

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

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

5 голосов
/ 16 ноября 2010

Здесь может помочь BULK INSERT.

2 голосов
/ 16 ноября 2010

Вот статья о массовой вставке с использованием C #: http://blogs.msdn.com/b/nikhilsi/archive/2008/06/11/bulk-insert-into-sql-from-c-app.aspx

Дополнительные соображения по поводу массовой вставки с C # находятся в вопросе переполнения стека: Каков наилучший способ массовой вставки базы данных из c #?

Надеюсь, это поможет.

1 голос
/ 17 ноября 2010

Это интересная проблема.Во-первых, как вы используете значения в базе данных?Участвуют ли они в последующих вычислениях или база данных просто "сбрасывает", чтобы сохранить результаты для дальнейшей обработки?Также ваше приложение / процесс работает 24 часа в сутки?
Почему я спрашиваю: если бы вы могли разделить операции «сохранить результаты» и «обработать результаты», вы могли бы добиться более высокой пропускной способности, «разбивая» данные за один сеанс и сохраняя их как один большой двоичный объект.Позже, в свободное от работы время, вы можете просмотреть и обработать и «развернуть» эти большие двоичные объекты в таблицы, например, с помощью задания или другого процесса.Теоретически, если все будет в порядке, вы можете хранить эти «промежуточные» двоичные объекты в двоичных файлах, а не непосредственно в базе данных, чтобы достичь максимально возможной скорости записи (ограниченной только файловой системой, ОС и аппаратным обеспечением диска).*

1 голос
/ 16 ноября 2010

Вы можете попробовать использовать Parallel For для вставки ...

... но я бы сначала попытался BULK INSERT или Batch commit ...

1 голос
/ 16 ноября 2010

Может быть, это может помочь вам

У меня есть пошаговое руководство по выполнению параллельных хранимых процедур в SQL здесь .

Вы можете комбинировать массовую вставку с этой.

...