【发布时间】:2014-12-17 11:51:15
【问题描述】:
我有一个大的 CSV 文件……我的硬盘上有 10 列、1 亿行、大约 6 GB 大小。 我想逐行读取这个 CSV 文件,然后使用 SQL 大容量复制将数据加载到 Microsoft SQL 服务器数据库中。 我在这里和互联网上阅读了几个主题。大多数人认为,并行读取 CSV 文件并不能提高效率,因为任务/线程会争用磁盘访问权限。
我想要做的是,从 CSV 中逐行读取并将其添加到阻止大小为 100K 行的集合中。一旦这个集合完全启动一个新的任务/线程,使用 SQLBuckCopy API 将数据写入 SQL 服务器。
我已经编写了这段代码,但在运行时遇到了一个错误,提示“尝试在具有挂起操作的对象上调用大容量复制”。这种情况看起来可以使用 .NET 4.0 TPL 轻松解决,但我无法让它工作。关于我做错了什么有什么建议吗?
public static void LoadCsvDataInParalleToSqlServer(string fileName, string connectionString, string table, DataColumn[] columns, bool truncate)
{
const int inputCollectionBufferSize = 1000000;
const int bulkInsertBufferCapacity = 100000;
const int bulkInsertConcurrency = 8;
var sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
var sqlBulkCopy = new SqlBulkCopy(sqlConnection.ConnectionString, SqlBulkCopyOptions.TableLock)
{
EnableStreaming = true,
BatchSize = bulkInsertBufferCapacity,
DestinationTableName = table,
BulkCopyTimeout = (24 * 60 * 60),
};
BlockingCollection<DataRow> rows = new BlockingCollection<DataRow>(inputCollectionBufferSize);
DataTable dataTable = new DataTable(table);
dataTable.Columns.AddRange(columns);
Task loadTask = Task.Factory.StartNew(() =>
{
foreach (DataRow row in ReadRows(fileName, dataTable))
{
rows.Add(row);
}
rows.CompleteAdding();
});
List<Task> insertTasks = new List<Task>(bulkInsertConcurrency);
for (int i = 0; i < bulkInsertConcurrency; i++)
{
insertTasks.Add(Task.Factory.StartNew((x) =>
{
List<DataRow> bulkInsertBuffer = new List<DataRow>(bulkInsertBufferCapacity);
foreach (DataRow row in rows.GetConsumingEnumerable())
{
if (bulkInsertBuffer.Count == bulkInsertBufferCapacity)
{
SqlBulkCopy bulkCopy = x as SqlBulkCopy;
var dataRows = bulkInsertBuffer.ToArray();
bulkCopy.WriteToServer(dataRows);
Console.WriteLine("Inserted rows " + bulkInsertBuffer.Count);
bulkInsertBuffer.Clear();
}
bulkInsertBuffer.Add(row);
}
},
sqlBulkCopy));
}
loadTask.Wait();
Task.WaitAll(insertTasks.ToArray());
}
private static IEnumerable<DataRow> ReadRows(string fileName, DataTable dataTable)
{
using (var textFieldParser = new TextFieldParser(fileName))
{
textFieldParser.TextFieldType = FieldType.Delimited;
textFieldParser.Delimiters = new[] { "," };
textFieldParser.HasFieldsEnclosedInQuotes = true;
while (!textFieldParser.EndOfData)
{
string[] cols = textFieldParser.ReadFields();
DataRow row = dataTable.NewRow();
for (int i = 0; i < cols.Length; i++)
{
if (string.IsNullOrEmpty(cols[i]))
{
row[i] = DBNull.Value;
}
else
{
row[i] = cols[i];
}
}
yield return row;
}
}
}
【问题讨论】:
-
与其花时间编写自己的工具,不如使用已经完成此任务的 ETL 工具,例如 SQL Server Integration Services。
-
您是否尝试过此代码的顺序版本并证明多线程的复杂性值得性能提升?
-
有很多优化批量插入的在线指南,即technet.microsoft.com/en-us/library/ms190421(v=sql.105).aspx。听起来您正在尝试解决尚未证明存在的问题。我建议你先简单地使用
BCP.EXE获得一个基线,然后尝试改进那个时间。 -
根据我在网上阅读的内容...SqlBulkCopy 比 SQL Server 拥有的内置数据导入工具快得多,我相信它在幕后使用了 SSIS。负载性能至关重要,因此我对为其编写自己的 lil 应用程序进行了调查
-
我有类似的卷,在我的情况下,我的 SQL 服务器的磁盘 IO 是瓶颈,所以我确实拆分了批次,但我没有并行。
标签: c# sql sql-server multithreading csv