簡體   English   中英

快速向 SQL Server 插入 200 萬行

[英]Insert 2 million rows into SQL Server quickly

我必須從文本文件中插入大約 200 萬行。

通過插入,我必須創建一些主表。

將如此大量的數據插入 SQL Server 的最佳和快速方法是什么?

  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 服務器 OpenXML 函數批量插入數據

但是它沒有用 200 萬條記錄進行測試,它只會消耗機器上的內存,因為您必須加載 200 萬條記錄並插入它。

您可以嘗試使用SqlBulkCopy類。

使您可以使用來自其他源的數據高效地批量加載 SQL Server 表。

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

重新 SqlBulkCopy 的解決方案:

我使用 StreamReader 來轉換和處理文本文件。 結果是我的對象列表。

我創建了一個類,而不是采用DatatableList<T>和一個緩沖區大小( 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 ) 的List<PuckDetection>

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

如果需要,可以修改BulkInsert類以添加列映射。 例如,您將 Identity 鍵作為第一列。(假設數據表中的列名與數據庫相同)

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

我使用 bcp 實用程序。 (批量復制程序)我每個月加載大約 150 萬條文本記錄。 每個文本記錄的寬度為 800 個字符。 在我的服務器上,將 150 萬條文本記錄添加到 SQL Server 表中大約需要 30 秒。

bcp 的說明位於http://msdn.microsoft.com/en-us/library/ms162802.aspx

我最近遇到了這種情況(超過 700 萬行)並通過 powershell 使用 sqlcmd(在將原始數據解析為 SQL 插入語句之后)一次以 5,000 段為單位(SQL 無法在一次作業中處理 700 萬行)甚至 500,000 行,除非將其分解為更小的 5K 片段。然后您可以一個接一個地運行每個 5K 腳本。)因為我需要利用 SQL Server 2012 Enterprise 中的新序列命令。 我找不到使用上述序列命令快速有效地插入 700 萬行數據的編程方法。

其次,一次性插入一百萬行或更多數據時要注意的一件事是插入過程中的 CPU 和內存消耗(主要是內存)。 SQL 將在不釋放所述進程的情況下,以這種規模的工作占用內存/CPU。 不用說,如果您的服務器上沒有足夠的處理能力或內存,您很容易在短時間內崩潰(我發現這很困難)。 如果您的內存消耗超過 70-75%,只需重新啟動服務器,進程就會恢復正常。

在我真正制定最終執行計划之前,我必須運行一堆反復試驗來查看我的服務器的限制是什么(考慮到有限的 CPU/內存資源)。 我建議您在將其投入生產之前在測試環境中做同樣的事情。

我嘗試過這種方法,它大大減少了我的數據庫插入執行時間。

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 的地方替換它。

我遇到了一個解決方案的問題,它應該與 ADO、Entity 和 Dapper 一起使用,所以制作了這個庫 它以以下形式生成批次:

    IEnumerable<(string SqlQuery, IEnumerable<SqlParameter> SqlParameters)>  
    IEnumerable<(string SqlQuery, DynamicParameters DapperDynamicParameters)> 

此鏈接包含說明。 對 SQL 注入是安全的,因為使用參數而不是串聯; 如果需要,您也可以通過可選參數將身份插入設置為 ON。

與 ADO.NET 一起使用:

using MsSqlHelpers;
// ...
var mapper = new MapperBuilder<Person>()
    .SetTableName("People")
    .AddMapping(person => person.FirstName, columnName: "Name")
    .AddMapping(person => person.LastName, columnName: "Surename")
    .AddMapping(person => person.DateOfBirth, columnName: "Birthday")
    .Build();
var people = new List<Person>()
{ 
    new Person()
    {
        FirstName = "John", 
        LastName = "Lennon", 
        DateOfBirth = new DateTime(1940, 10, 9) 
    },
    new Person()
    {
        FirstName = "Paul", 
        LastName = "McCartney", 
        DateOfBirth = new DateTime(1942, 6, 18) 
    },
};
var connectionString = "Server=SERVER_ADDRESS;Database=DATABASE_NAME;User Id=USERNAME;Password=PASSWORD;";
var sqlQueriesAndParameters = new MsSqlQueryGenerator()
    .GenerateParametrizedBulkInserts(mapper, people);

using (var sqlConnection = new SqlConnection(connectionString))
{
    sqlConnection.Open();
    
    // Default batch size: 1000 rows or (2100-1) parameters per insert.
    foreach (var (SqlQuery, SqlParameters) in sqlQueriesAndParameters)
    {
        using (SqlCommand sqlCommand = new SqlCommand(SqlQuery, sqlConnection))
        {
            sqlCommand.Parameters.AddRange(SqlParameters.ToArray());
            sqlCommand.ExecuteNonQuery();
        }
    }
}

與 Dapper 一起使用:

using MsSqlHelpers;
// ...
var mapper = new MapperBuilder<Person>()
    .SetTableName("People")
    .AddMapping(person => person.FirstName, columnName: "Name")
    .AddMapping(person => person.LastName, columnName: "Surename")
    .AddMapping(person => person.DateOfBirth, columnName: "Birthday")
    .Build();
var people = new List<Person>()
{ 
    new Person()
    {
        FirstName = "John", 
        LastName = "Lennon", 
        DateOfBirth = new DateTime(1940, 10, 9) 
    },
    new Person()
    { 
        FirstName = "Paul", 
        LastName = "McCartney", 
        DateOfBirth = new DateTime(1942, 6, 18) 
    },
};
var connectionString = "Server=SERVER_ADDRESS;Database=DATABASE_NAME;User Id=USERNAME;Password=PASSWORD;";
var sqlQueriesAndDapperParameters = new MsSqlQueryGenerator()
    .GenerateDapperParametrizedBulkInserts(mapper, people);

using (var sqlConnection = new SqlConnection(connectionString))
{
    // Default batch size: 1000 rows or (2100-1) parameters per insert.
    foreach (var (SqlQuery, DapperDynamicParameters) in sqlQueriesAndDapperParameters)
    {
        sqlConnection.Execute(SqlQuery, DapperDynamicParameters);
    }
}

與實體框架一起使用:

using MsSqlHelpers;
// ...
var mapper = new MapperBuilder<Person>()
    .SetTableName("People")
    .AddMapping(person => person.FirstName, columnName: "Name")
    .AddMapping(person => person.LastName, columnName: "Surename")
    .AddMapping(person => person.DateOfBirth, columnName: "Birthday")
    .Build();
var people = new List<Person>()
{ 
    new Person() 
    { 
        FirstName = "John", 
        LastName = "Lennon", 
        DateOfBirth = new DateTime(1940, 10, 9) 
    },
    new Person()
    { 
        FirstName = "Paul", 
        LastName = "McCartney", 
        DateOfBirth = new DateTime(1942, 6, 18) 
    },
};
var sqlQueriesAndParameters = new MsSqlQueryGenerator()
    .GenerateParametrizedBulkInserts(mapper, people);

// Default batch size: 1000 rows or (2100-1) parameters per insert.
foreach (var (SqlQuery, SqlParameters) in sqlQueriesAndParameters)
{
    _context.Database.ExecuteSqlRaw(SqlQuery, SqlParameters);
    // Depracated but still works: _context.Database.ExecuteSqlCommand(SqlQuery, SqlParameters);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM