Одновременные вставки SQL BULK генерируют сбои при использовании опции ErrorFile - PullRequest
4 голосов
/ 20 января 2012

Windows Server 2008 R2 Enterprise, SQL Server 2008 X64, SP3, версия для разработчиков

Я создаю и динамически выполняю (через sp_executesql) команду BULK INSERT. Общая форма:

BULK INSERT #HeaderRowCheck
 from "\\Server\Share\Develop\PKelley\StressTesting\101\DataSet.csv"
 with
 (
   lastrow = 1
  ,rowterminator = '\n'
  ,tablock
  ,maxerrors = 0
  ,errorfile = 'C:\SQL_Packages\TempFiles\#HeaderRowCheck_257626FB-A5CD-41B8-B862-FAF8C591C7A9.log'
 )

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

Внешний процесс (был агентом SQL, теперь является службой WCF) запускает DTEXEC, который запускает пакет служб SSIS, который вызывает хранимые процедуры в базе данных, проходящей через наборы, строит запрос и запускает его для каждого. Может быть запущено до четырех загрузок одновременно из / в заданную базу данных, и несколько баз данных на экземпляре SQL могут запускать это одновременно - хотя исторически объем был низким, и у нас обычно была только одна экземпляр запускает это одновременно. Мы много делаем это, и это работало почти без сбоев уже более двух лет - безопасность настроена правильно, необходимые файлы и папки существуют, все как обычно. (Удачи? Мне нравится думать, что нет.)

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

Error #4861 encountered while loading header information from file "DataSet.csv": Cannot bulk load because the file "C:\SQL_Packages\TempFiles\#HeaderRowCheck_D0070742-76A5-4175-A1A7-16494103EF25.log" could not be opened. Operating system error code 80(The file exists.).

От запуска к запуску ошибка не возникает для одного и того же файла, набора данных или общей точки обработки.

На первый взгляд кажется, что два процесса пытаются получить доступ к одному и тому же файлу ошибок, что означает, что они независимо генерируют один и тот же guid (!). Я понимаю, что это почти невозможно. Альтернативная теория заключается в том, что одновременно происходит так много всего (возможно, одновременно выполняется до 32 одновременных команд BULK INSERT), что SQL и / или ОС каким-то образом путаются (я администратор, а не администратор сети). Я мог бы обойти это, построив свой блок try-catch для проверки на ошибку 4861 и повторив попытки до трех раз, но я бы предпочел избежать такой путаницы.

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

Кто-нибудь знает, что может происходить?

1023 * Philip *

1 Ответ

6 голосов
/ 03 февраля 2012

Я открыл дело в службе технической поддержки Microsoft, и после немалого количества взад-вперед Pradeep M.M. (Технический руководитель службы поддержки SQL Server) с этим справился.

Общий процесс: чтение списка файлов в папке, и один за другим выполнить серию массовых вставок в эти файлы (сначала прочитать первую строку, которую мы анализируем по столбцам, а затем прочитать данные из вторая + строчки). Все массовые вставки используют опцию «ErrorFile», чтобы предоставить пользователям какую информацию мы можем, когда их данные неверно отформатированы. Процесс работал более 3 лет, но в последних условиях стресс-тестирования (до 8 одновременных запусков, выполняемых одним экземпляром SQL Server, со всеми форматированными файлами), мы получили ошибки, перечисленные выше.

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

В соответствии с Pradeep, вот пошаговый процесс работы Bulk Insert:

  1. BULK INSERT Команда отправлена ​​и проанализирована на наличие синтаксических ошибок
  2. Затем команда BULK INSERT компилируется для генерации выполнения. План для того же
  3. На этапе компиляции, если в запросе, если мы указали ERRORFILE параметр, то мы создадим ErrorFile.log и ErrorFile.Error.Txt в указанную папку (важно что нужно понять, так это файл размером 0 КБ)
  4. Как только создание файла завершено, мы удаляем оба файла, используя Вызовы API Windows
  5. Как только план выполнения будет готов, мы перейдем к этапу выполнения. и попробуйте выполнить команду Bulk Insert, как часть этого мы будем заново создайте ErrorFile.log и ErrorFile.Error.Txt в папке указанное местоположение (согласно документации по книгам в Интернете ошибка файлы не должны быть там в этом месте, иначе мы не сможем исполнение http://msdn.microsoft.com/en-us/library/ms188365.aspx
  6. После завершения выполнения, если в Массовая вставка соответствующих ошибок регистрируется в файлах ошибок создано, если нет ошибок, эти 2 файла будут удалены.

Запуск ProcMon (Process Monitor) во время неудачных запусков показал, что ErrorFile был успешно создан и открыт на шаге 3, но НЕ был закрыт на шаге 4, в результате чего на шаге 5 возникла ошибка, которую мы видели. (Для успешных запусков файл был создан и закрыт, как и ожидалось.)

Дальнейший анализ ProcMon показал, что после процесса массовой вставки другой процесс, выполняющий CMD.EXE, выдавал над файлом операции «закрывать дескриптор». Мы используем подпрограмму, включающую xp_cmdshell, для получения списка файлов, которые должны быть обработаны, и это будет причиной процесса CMD.EXE. Вот кикер:

… существует некоторая бизнес-логика, которая запускает CMD.EXE внутри SQL Server, и, поскольку CMD.EXE является дочерним процессом, он наследует все дескрипторы, открытые родительским процессом (так что, вероятно, это какая-то проблема с синхронизацией в CMD. EXE содержит дескрипторы файлов, которые открываются при запуске, и все эти файлы, дескриптор которых наследуется CMD.EXE, не могут быть удалены и могут быть освобождены только после уничтожения CMD.EXE)

И это было все. Один запуск никогда не решает эту проблему, так как его вызов xp_cmdshell завершается до выпуска массовых вставок. Но при параллельных запусках, особенно при многих параллельных запусках (я столкнулся с проблемой только при 5 или более ходах), возникла проблема с синхронизацией:

  1. Один из пакетов служб SSIS выполняет и вызывает хранимую процедуру который внутренне использует XP_CMDSHELL и запускает CMD.EXE для перечисления Файлы
  2. То же соединение с сервером SQL завершает перечисление файлов а затем запускает групповую операцию вставки фаза для команды BULK INSERT
  3. В соответствии с дизайном Bulk Insert мы создаем ErrorFile во время фазы компиляцииSE, а затем удалить его после компиляции фаза выполнена
  4. В то же время запускается другой пакет служб SSIS и он вызывает хранимая процедура, которая внутренне использует XP_CMDSHELL и запускает CMD.EXE для перечисления всех файлов
  5. CMD.EXE - это дочерний процесс, запущенный под родительским Обработайте SQLServr.exe, поэтому по умолчанию он наследует все дескрипторы. созданный SQLServr.exe (так что этот процесс получает все дескрипторы для ОШИБКА, созданная BULK INSERT в первом Connection)
  6. Теперь в первом соединении фаза компиляции завершена и следовательно мы пытаемся удалить файл, в течение которого мы должны закрыть все ручки, мы видим, что CMD.EXE держит дескриптор файл, и он все еще открыт, и, следовательно, мы не можем удалить файл. Так без удаления файла мы переходим к этапу выполнения и в На этапе выполнения мы пытаемся создать новый ОШИБКА с то же имя, но так как файл уже существует, мы терпим неудачу с ошибкой «Код ошибки операционной системы 80 (файл существует.).»

Мой краткосрочный обходной путь состоял в том, чтобы (1) реализовать цикл повторных попыток, сгенерировать новое имя ErrorFile и попробовать новую массовую вставку до трех раз, прежде чем отказаться, и (2) создать другую подпрограмму на наших ночных процессах для удаления все файлы, найденные в нашей папке «ErrorFile».

Долгосрочное исправление состоит в том, чтобы пересмотреть наш код, чтобы не выводить список файлов через xp_cmdshell. Это может показаться возможным, поскольку весь процесс ETL обернут и управляется пакетом служб SSIS; в качестве альтернативы, подпрограммы CLR могут быть встроены и проработаны. На данный момент, учитывая нашу ожидаемую рабочую нагрузку, обходной путь является достаточным (особенно учитывая все остальное, над чем мы сейчас работаем), поэтому может потребоваться немного времени, прежде чем мы реализуем финальную версию. исправить.

Добавлено для потомков, на случай, если это когда-нибудь случится с вами!

...