快速将200万行插入SQL Server


Answers:


58

您可以尝试SqlBulkCopy上课。

使您可以有效地用另一个来源的数据批量加载SQL Server表。

有一篇很酷的博客文章介绍了如何使用它。


另请注意(同样在SQLBulCopy链接中提到)If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data.
nam

70
  1. 我认为最好在DataSet中读取文本文件的数据

  2. 尝试 SqlBulkCopy的-从C#应用程序批量插入到SQL

    // connect to SQL
    using (SqlConnection connection = new SqlConnection(connString))
    {
        // make sure to enable triggers
        // more on triggers in next post
        SqlBulkCopy bulkCopy = new SqlBulkCopy(
            connection, 
            SqlBulkCopyOptions.TableLock | 
            SqlBulkCopyOptions.FireTriggers | 
            SqlBulkCopyOptions.UseInternalTransaction,
            null
            );
    
        // set the destination table name
        bulkCopy.DestinationTableName = this.tableName;
        connection.Open();
    
        // write the data in the "dataTable"
        bulkCopy.WriteToServer(dataTable);
        connection.Close();
    }
    // reset
    this.dataTable.Clear();
    

要么

在顶部执行步骤1之后

  1. 从数据集创建XML
  2. 将XML传递到数据库并批量插入

您可以查看本文的详细信息:使用C#DataTable和SQL Server OpenXML函数批量插入数据

但是它没有经过200万条记录的测试,它可以运行,但是会消耗机器上的内存,因为您必须加载200万条记录并将其插入。


1
我知道这已经很晚了,但是对于大约200万行(或更多),如果有足够的列(25+),则OutOfMemoryException在填充数据集/数据表时几乎不可避免地会在某个点生成代码。
Razort4x

18
您可以设置缓冲区以避免内存不足异常。对于文本文件,我使用了File.ReadLines(file).Skip(X).Take(100000).ToList()。每隔100k,我将重置并移动到下一个100k。效果很好,非常快。
杰森·福利亚

如果源数据来自Sql Server表,该怎么办。假设该表有3000万行,我们还可以使用批量复制吗?简单Insert into table1 Select * from table2不会更快吗?
Madhav Shenoy

28

重新解决SqlBulkCopy的解决方案:

我使用StreamReader来转换和处理文本文件。结果是我的物品清单。

我创建了比takeDatatable或aList<T>和Buffer size(CommitBatchSize)大的类。它将使用扩展名将列表转换为数据表(在第二类中)。

它运作非常快。在我的PC上,我能够在不到10秒的时间内插入超过1000万条复杂记录。

这是课程:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DAL
{

public class BulkUploadToSql<T>
{
    public IList<T> InternalStore { get; set; }
    public string TableName { get; set; }
    public int CommitBatchSize { get; set; }=1000;
    public string ConnectionString { get; set; }

    public void Commit()
    {
        if (InternalStore.Count>0)
        {
            DataTable dt;
            int numberOfPages = (InternalStore.Count / CommitBatchSize)  + (InternalStore.Count % CommitBatchSize == 0 ? 0 : 1);
            for (int pageIndex = 0; pageIndex < numberOfPages; pageIndex++)
                {
                    dt= InternalStore.Skip(pageIndex * CommitBatchSize).Take(CommitBatchSize).ToDataTable();
                BulkInsert(dt);
                }
        } 
    }

    public void BulkInsert(DataTable dt)
    {
        using (SqlConnection connection = new SqlConnection(ConnectionString))
        {
            // make sure to enable triggers
            // more on triggers in next post
            SqlBulkCopy bulkCopy =
                new SqlBulkCopy
                (
                connection,
                SqlBulkCopyOptions.TableLock |
                SqlBulkCopyOptions.FireTriggers |
                SqlBulkCopyOptions.UseInternalTransaction,
                null
                );

            // set the destination table name
            bulkCopy.DestinationTableName = TableName;
            connection.Open();

            // write the data in the "dataTable"
            bulkCopy.WriteToServer(dt);
            connection.Close();
        }
        // reset
        //this.dataTable.Clear();
    }

}

public static class BulkUploadToSqlHelper
{
    public static DataTable ToDataTable<T>(this IEnumerable<T> data)
    {
        PropertyDescriptorCollection properties =
            TypeDescriptor.GetProperties(typeof(T));
        DataTable table = new DataTable();
        foreach (PropertyDescriptor prop in properties)
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
                row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
            table.Rows.Add(row);
        }
        return table;
    }
}

}

以下是我要插入自定义对象List<PuckDetection>ListDetections)列表的示例:

var objBulk = new BulkUploadToSql<PuckDetection>()
{
        InternalStore = ListDetections,
        TableName= "PuckDetections",
        CommitBatchSize=1000,
        ConnectionString="ENTER YOU CONNECTION STRING"
};
objBulk.Commit();

BulkInsert类可以修改,如果需要添加的列映射。例如,您有一个身份密钥作为第一列(假设数据表中的列名称与数据库相同)

//ADD COLUMN MAPPING
foreach (DataColumn col in dt.Columns)
{
        bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}


3

我最近遇到了这种情况(超过700万行),并通过powershell(在将原始数据解析为SQL插入语句之后)使用sqlcmd进行了优化,一次只能分割5,000个段(SQL无法一次完成处理700万行)或什至是500,000行,除非将其分解成更小的5K片段。然后您可以一个接一个地运行每个5K脚本。)我需要利用SQL Server 2012 Enterprise中的新sequence命令。我找不到一种编程方式,可以通过上述sequence命令快速有效地插入700万行数据。

其次,一次插入一百万行或更多数据时要注意的事情之一是插入过程中的CPU和内存消耗(主要是内存)。SQL将在不释放上述进程的情况下吞噬如此大量的内存/ CPU。不用说,如果服务器上没有足够的处理能力或内存,则很容易在短时间内使服务器崩溃(我发现这很困难)。如果到达内存消耗超过70-75%的地步,只需重新启动服务器,进程就会恢复正常。

在实际制定最终执行计划之前,我必须进行大量的试验和错误测试,以查看服务器的限制(考虑到要使用的有限CPU /内存资源)。我建议您在测试环境中进行同样的操作,然后再将其投入生产。


1
7M行花了多长时间?我要插入大约3000万行。现在,我正在通过存储过程和DataTable推动它们。
亚历山大·布瑞斯布瓦

1
同时运行小批量需要花费五到六个小时。请记住,当我利用SQL 2012中的新SEQUENCE命令时,我只是直接执行了T-SQL插入命令,无法在T-SQL之外找到有关如何自动执行此过程的信息。
Techie Joe

0

我尝试使用此方法,它大大减少了我的数据库插入执行时间。

List<string> toinsert = new List<string>();
StringBuilder insertCmd = new StringBuilder("INSERT INTO tabblename (col1, col2, col3) VALUES ");

foreach (var row in rows)
{
      // the point here is to keep values quoted and avoid SQL injection
      var first = row.First.Replace("'", "''")
      var second = row.Second.Replace("'", "''")
      var third = row.Third.Replace("'", "''")

      toinsert.Add(string.Format("( '{0}', '{1}', '{2}' )", first, second, third));
}
if (toinsert.Count != 0)
{
      insertCmd.Append(string.Join(",", toinsert));
      insertCmd.Append(";");
}
using (MySqlCommand myCmd = new MySqlCommand(insertCmd.ToString(), SQLconnectionObject))
{
      myCmd.CommandType = CommandType.Text;
      myCmd.ExecuteNonQuery();
}

*创建SQL连接对象,并在我编写SQLconnectionObject的地方替换它。


3
小心!这可以通过SQL注入加以利用
Andre Soares
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.