В настоящее время я пытаюсь импортировать CSV-файл размером 20 ГБ (примерно 64 миллиона строк, 58 столбцов) в базу данных ms sql.
Сначала я попытался сделать это с помощью SSIS, но это было так медленно Я решил попробовать вместо этого использовать Powershell и нашел хороший запрос здесь:
Высокопроизводительный импорт CSV
Запрос очень быстрый, мне удается вставить примерно 1 миллион строк в минуту. Однако , мне нужно иметь возможность обрабатывать разделители, заключенные в кавычки, например: Столбец1, «Автомобиль, самолет, лодка», Столбец3
Я сделал это с помощью регулярных выражений по рекомендации авторов: переключение:
$null = $datatable.Rows.Add($line.Split($csvdelimiter))
на:
$null = $datatable.Rows.Add($([regex]::Split($line, $csvSplit, $regexOptions)))
Полный запрос:
# Database variables
$sqlserver = "server"
$database = "database"
$table = "tablename"
# CSV variables
$csvfile = "filepath"
$csvdelimiter = ","
$firstRowColumnNames = $true
$fieldsEnclosedInQuotes = $true
# Handling of regex for comma problem
if ($fieldsEnclosedInQuotes) {
$csvSplit = "($csvdelimiter)"
$csvsplit += '(?=(?:[^"]|"[^"]*")*$)'
} else { $csvsplit = $csvdelimiter }
$regexOptions = [System.Text.RegularExpressions.RegexOptions]::ExplicitCapture
################### No need to modify anything below ###################
Write-Host "Script started..."
$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
[void][Reflection.Assembly]::LoadWithPartialName("System.Data")
[void][Reflection.Assembly]::LoadWithPartialName("System.Data.SqlClient")
# 50k worked fastest and kept memory usage to a minimum
$batchsize = 50000
# Build the sqlbulkcopy connection, and set the timeout to infinite
$connectionstring = "Data Source=$sqlserver;Integrated Security=true;Initial Catalog=$database;"
$bulkcopy = New-Object Data.SqlClient.SqlBulkCopy($connectionstring, [System.Data.SqlClient.SqlBulkCopyOptions]::TableLock)
$bulkcopy.DestinationTableName = $table
$bulkcopy.bulkcopyTimeout = 0
$bulkcopy.batchsize = $batchsize
# Create the datatable, and autogenerate the columns.
$datatable = New-Object System.Data.DataTable
# Open the text file from disk
$reader = New-Object System.IO.StreamReader($csvfile)
$firstline = (Get-Content $csvfile -First 1)
$columns = [regex]::Split($firstline, $csvSplit, $regexOptions)
if ($firstRowColumnNames -eq $true) { $null = $reader.readLine() }
foreach ($column in $columns) {
$null = $datatable.Columns.Add()
}
# Read in the data, line by line
while (($line = $reader.ReadLine()) -ne $null) {
$null = $datatable.Rows.Add($([regex]::Split($line, $csvSplit, $regexOptions)))
$i++; if (($i % $batchsize) -eq 0) {
$bulkcopy.WriteToServer($datatable)
Write-Host "$i rows have been inserted in $($elapsed.Elapsed.ToString())."
$datatable.Clear()
}
}
# add in all the remaining rows since the last clear
if($datatable.rows.count -gt 0) {
$bulkcopy.writetoserver($datatable)
$datatable.clear()
}
# Clean Up
$reader.Close(); $reader.Dispose()
$bulkcopy.Close(); $bulkcopy.Dispose()
$datatable.Dispose()
Write-Host "Script complete. $i rows have been inserted into the database."
Write-Host "Total Elapsed Time: $($elapsed.Elapsed.ToString())"
# Sometimes the Garbage Collector takes too long to clear the huge datatable.
[System.GC]::Collect()
pause
Это занимает гораздо больше времени с регулярное выражение:
24 секунды на 50 000 тыс. строк (с обработкой разделителей, встроенных в квоты)
2 секунды на 50 000 тыс. строк (без обработка)
Я что-то не так делаю? Является ли регулярное выражение правильным путем к этому? Могу ли я улучшить производительность запросов каким-либо образом или эта производительность потеряла то, что я должен принять?
Обновление: добавлен полностью запрос