SqlBulkCopy和DataTable的性能问题

时间:2014-02-14 12:27:02

标签: c# performance datatable streamreader sqlbulkcopy

我需要有效地将大量数据从文件导入数据库。 我有几个包含该数据的rrf文件,文件的大小可以是> 400mb,最终可能是>从文件到数据库的200万条记录。

我做了什么:

  1. 我正在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);
        }
    }
    
  2. 在事务中(这是一个关键点),我将DataTable的数据通过SqlBulkCopy插入数据库。

    var bcp = new SqlBulkCopy(_sqlConnection, SqlBulkCopyOptions.Default, transaction);
    bcp.DestinationTableName = tableName;          
    bcp.WriteToServer(dataTable);
    
  3. 问题在于,由于每个DataTable可能包含超过200万条记录,因此DataTable存储需要大量内存(大约2 GB)。

    这样的事情
    dataTable.Dispose();
    dataTable = null;
    

    GC.Collect();
    GC.SuppressFinalize();
    

    实际上没有帮助。

    Batchsize的{​​{1}}属性与它无关,所有内存都由存储应插入行的SqlBulkCopy占用。

    我想知道,是否有有效的方法来读取数据并使用DataTable

2 个答案:

答案 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开销。