我需要有效地将大量数据从文件导入数据库。 我有几个包含该数据的rrf文件,文件的大小可以是> 400mb,最终可能是>从文件到数据库的200万条记录。
我做了什么:
我正在DataTable中阅读所需的记录。
using (StreamReader streamReader = new StreamReader(filePath))
{
IEnumerable<string> values = new List<string>();
while (!streamReader.EndOfStream)
{
string line = streamReader.ReadLine().Split('|');
int index = 0;
var dataRow = dataTable.NewRow();
foreach (var value in values)
{
dataRow[index] = value;
index++;
}
dataTable.Rows.Add(dataRow);
}
}
在事务中(这是一个关键点),我将DataTable
的数据通过SqlBulkCopy
插入数据库。
var bcp = new SqlBulkCopy(_sqlConnection, SqlBulkCopyOptions.Default, transaction);
bcp.DestinationTableName = tableName;
bcp.WriteToServer(dataTable);
问题在于,由于每个DataTable
可能包含超过200万条记录,因此DataTable
存储需要大量内存(大约2 GB)。
像
这样的事情dataTable.Dispose();
dataTable = null;
或
GC.Collect();
GC.SuppressFinalize();
实际上没有帮助。
Batchsize
的{{1}}属性与它无关,所有内存都由存储应插入行的SqlBulkCopy
占用。
我想知道,是否有有效的方法来读取数据并使用DataTable
?
答案 0 :(得分:1)
根据我的经验,批量插入的最佳DataTable大小介于60,000行和100,000行之间。此外,我发现重用DataTable比克隆一个新的慢。 DataTable.Rows.Clear()不会清除约束,并且在第一次批量插入后添加新行要慢得多。 DataTable.Clear()要好得多,但从新的DataTable开始,每个批量都是最快的。
所以你的代码看起来像是:
int batchSize = 65000;
bool lastLine = streamReader.EndOfStream;
if (dataTable.Rows.Count == batchSize || lastLine) {
// do bulk insert
DataTable temp = dataTable.Clone();
dataTable.Dispose();
dataTable = temp;
}
除此之外,您还可以将批量插入分隔到自己的线程中。因此,您的文件读取线程将生成批量插入线程将使用的DataTable对象。您必须添加信号量以确保您的文件读取线程不会过度生成,否则您将使用太多内存。
以下是生产/消费代码的示例。随意改进它。
您可以使用休眠时间来查看代码如何在生产者方或消费者方等待。
public static void produce() {
DataObject o2 = new DataObject();
Thread t = new Thread(consume);
t.Start(o2);
for (int i = 0; i < 10; i++) {
if (o2.queue.Count > 2) {
lock(o2.sb)
o2.sb.AppendLine("3 items read, waiting for consume to finish");
o2.wait.Set();
o2.parentWait.WaitOne();
o2.parentWait.Reset();
}
Thread.Sleep(500); // simulate reading data
lock(o2.sb)
o2.sb.AppendLine("Read file: " + i);
lock(o2.queue) {
o2.queue.Add(i);
}
o2.wait.Set();
}
o2.finished = true;
o2.wait.Set();
}
public class DataObject {
public bool finished = false;
public List<int> queue = new List<int>();
public ManualResetEvent wait = new ManualResetEvent(false);
public ManualResetEvent parentWait = new ManualResetEvent(false);
public StringBuilder sb = new StringBuilder();
}
public static void consume(Object o) {
DataObject o2 = (DataObject) o;
while (true) {
if (o2.finished && o2.queue.Count == 0)
break;
if (o2.queue.Count == 0) {
lock(o2.sb)
o2.sb.AppendLine("Nothing in queue, waiting for produce.");
o2.wait.WaitOne();
o2.wait.Reset();
}
Object val = null;
lock(o2.queue) {
val = o2.queue[0];
o2.queue.RemoveAt(0);
}
o2.parentWait.Set(); // notify parent to produce more
lock(o2.sb)
o2.sb.AppendLine("Loading data to SQL: " + val);
Thread.Sleep(500);
}
}
答案 1 :(得分:0)
尝试使用Marc Gravell's FastMember而不是DataTable / DataRow,因为它有助于避免.NET Reflection的CPU开销。