使用 C# 将 CSV 数据(300 万数据)移动到 DataTable 时出现内存不足异常
Out of memory exception while moving CSV data (3 million data) into DataTable using c#
我有 300 万行和 40 列的 CSV 文件。我想使用 SqlBulkCopy 概念将 CSV 数据移动到 SQL 服务器。为此,我使用 CSV reader 将每一列和每一行移动到数据 table 中。使用 while 循环在数据 table 中插入行时,出现内存异常。
注意:438184(下例的 i 值)记录插入成功。之后我得到了内存异常。
SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand SqlCmd = new SqlCommand();
SqlCmd.CommandTimeout = 0;
SqlCmd.Connection = con;
SqlTransaction transaction = con.BeginTransaction();
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
StreamReader streamReader = new StreamReader(fileStream);
CsvReader reader = new CsvReader(streamReader);
DataTable table = new DataTable();
reader.ReadHeaderRecord();
foreach (var c in reader.HeaderRecord)
{
table.Columns.Add(c);
}
table.Rows.Clear();
int i = 0;
while (reader.HasMoreRecords)
{
DataRecord record = reader.ReadDataRecord();
table.Rows.Add(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8], record[9], record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[17], record[18], record[19], record[20], record[21], record[22], record[23], record[24], record[25], record[26], record[27], record[28], record[29], record[30], record[31], record[32], record[33], record[34], record[35], record[36], record[37], record[38], record[39]);
i++;
}
SqlBulkCopy copy = new SqlBulkCopy(con, SqlBulkCopyOptions.KeepIdentity, transaction);
copy.DestinationTableName = "SampleCSV";
copy.WriteToServer(table);
transaction.Commit();
con.Close();
谁能建议我如何解决这个问题?
您可以指定批量大小
查看 SqlBulkCopy.BatchSize 属性
的更多详细信息
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy.batchsize(v=vs.110).aspx
或者,如果您的目标 table 模式是固定的,那么您可以使用 SSIS 包。
SqlBulkCopy copy = new SqlBulkCopy(con, SqlBulkCopyOptions.KeepIdentity, transaction);
//you can define any Banch of Data insert
copy.BatchSize(10000);
copy.DestinationTableName = "SampleCSV";
copy.WriteToServer(table);
transaction.Commit();
con.Close();
如果您遇到内存异常,这意味着您无法一次加载 DataTable 中的所有行。您将需要每 X
行执行一次 SqlBulkCopy 并在处理更多之前清除 DataTable
。
例如,以下代码将在 DataTable
中每加载 100,000 行执行一次 SqlBulkCopy,然后在加载更多行之前清除所有行。
int i = 0;
while (reader.HasMoreRecords)
{
// INSERT rows every x
if(i % 100000 == 0)
{
ExecuteBulkCopy(conn, transaction, table);
table.Rows.Clear();
}
DataRecord record = reader.ReadDataRecord();
table.Rows.Add(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8], record[9], record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[17], record[18], record[19], record[20], record[21], record[22], record[23], record[24], record[25], record[26], record[27], record[28], record[29], record[30], record[31], record[32], record[33], record[34], record[35], record[36], record[37], record[38], record[39]);
i++;
}
// INSERT remaining row
if(table.Rows.Count > 0)
{
ExecuteBulkCopy(conn, transaction, table);
}
public void ExecuteBulkCopy(SqlConnection con, SqlTransaction transaction, DataTable table)
{
SqlBulkCopy copy = new SqlBulkCopy(con, SqlBulkCopyOptions.KeepIdentity, transaction);
copy.DestinationTableName = "SampleCSV";
copy.WriteToServer(table);
transaction.Commit();
}
编辑:回答子问题
Can I reduce this time if I use Bulk insert or any other?
SqlBulkCopy
是最快的插入方式。甚至我的库 .NET Bulk Operations 在后台使用 SqlBulkCopy。
300万条数据20分钟很慢,不过根据你的table(char列、触发器、索引等)也可以正常
这里要添加两个对性能影响最大的配置。
使用 BatchSize
以5000为例
使用较小的批次多次插入通常比非常大的批次更快。
使用 TableLock 选项
new SqlBulkCopy(connectionString, SqlBulkCopyOptions.TableLock))
通过锁定 table,SqlBulkCopy 将执行得更快。
我有 300 万行和 40 列的 CSV 文件。我想使用 SqlBulkCopy 概念将 CSV 数据移动到 SQL 服务器。为此,我使用 CSV reader 将每一列和每一行移动到数据 table 中。使用 while 循环在数据 table 中插入行时,出现内存异常。 注意:438184(下例的 i 值)记录插入成功。之后我得到了内存异常。
SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand SqlCmd = new SqlCommand();
SqlCmd.CommandTimeout = 0;
SqlCmd.Connection = con;
SqlTransaction transaction = con.BeginTransaction();
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
StreamReader streamReader = new StreamReader(fileStream);
CsvReader reader = new CsvReader(streamReader);
DataTable table = new DataTable();
reader.ReadHeaderRecord();
foreach (var c in reader.HeaderRecord)
{
table.Columns.Add(c);
}
table.Rows.Clear();
int i = 0;
while (reader.HasMoreRecords)
{
DataRecord record = reader.ReadDataRecord();
table.Rows.Add(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8], record[9], record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[17], record[18], record[19], record[20], record[21], record[22], record[23], record[24], record[25], record[26], record[27], record[28], record[29], record[30], record[31], record[32], record[33], record[34], record[35], record[36], record[37], record[38], record[39]);
i++;
}
SqlBulkCopy copy = new SqlBulkCopy(con, SqlBulkCopyOptions.KeepIdentity, transaction);
copy.DestinationTableName = "SampleCSV";
copy.WriteToServer(table);
transaction.Commit();
con.Close();
谁能建议我如何解决这个问题?
您可以指定批量大小
查看 SqlBulkCopy.BatchSize 属性
的更多详细信息https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy.batchsize(v=vs.110).aspx
或者,如果您的目标 table 模式是固定的,那么您可以使用 SSIS 包。
SqlBulkCopy copy = new SqlBulkCopy(con, SqlBulkCopyOptions.KeepIdentity, transaction);
//you can define any Banch of Data insert
copy.BatchSize(10000);
copy.DestinationTableName = "SampleCSV";
copy.WriteToServer(table);
transaction.Commit();
con.Close();
如果您遇到内存异常,这意味着您无法一次加载 DataTable 中的所有行。您将需要每 X
行执行一次 SqlBulkCopy 并在处理更多之前清除 DataTable
。
例如,以下代码将在 DataTable
中每加载 100,000 行执行一次 SqlBulkCopy,然后在加载更多行之前清除所有行。
int i = 0;
while (reader.HasMoreRecords)
{
// INSERT rows every x
if(i % 100000 == 0)
{
ExecuteBulkCopy(conn, transaction, table);
table.Rows.Clear();
}
DataRecord record = reader.ReadDataRecord();
table.Rows.Add(record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8], record[9], record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[17], record[18], record[19], record[20], record[21], record[22], record[23], record[24], record[25], record[26], record[27], record[28], record[29], record[30], record[31], record[32], record[33], record[34], record[35], record[36], record[37], record[38], record[39]);
i++;
}
// INSERT remaining row
if(table.Rows.Count > 0)
{
ExecuteBulkCopy(conn, transaction, table);
}
public void ExecuteBulkCopy(SqlConnection con, SqlTransaction transaction, DataTable table)
{
SqlBulkCopy copy = new SqlBulkCopy(con, SqlBulkCopyOptions.KeepIdentity, transaction);
copy.DestinationTableName = "SampleCSV";
copy.WriteToServer(table);
transaction.Commit();
}
编辑:回答子问题
Can I reduce this time if I use Bulk insert or any other?
SqlBulkCopy
是最快的插入方式。甚至我的库 .NET Bulk Operations 在后台使用 SqlBulkCopy。
300万条数据20分钟很慢,不过根据你的table(char列、触发器、索引等)也可以正常
这里要添加两个对性能影响最大的配置。
使用 BatchSize
以5000为例 使用较小的批次多次插入通常比非常大的批次更快。
使用 TableLock 选项
new SqlBulkCopy(connectionString, SqlBulkCopyOptions.TableLock))
通过锁定 table,SqlBulkCopy 将执行得更快。