Быстро вставьте 2 миллиона строк в SQL Server

Я должен вставить около 2 миллионов строк из текстового файла.

И со вставкой я должен создать несколько мастер-таблиц.

Каков наилучший и быстрый способ вставить такой большой набор данных в SQL Server?

9 ответов

Решение

Вы можете попробовать с SqlBulkCopy учебный класс.

Позволяет эффективно массово загружать таблицу SQL Server с данными из другого источника.

Есть классная запись в блоге о том, как вы можете ее использовать.

  1. Я думаю, что лучше читать данные текстового файла в DataSet

  2. Попробуйте SqlBulkCopy - массовая вставка в SQL из приложения C#

    // 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 из DataSet
  2. Передайте XML в базу данных и выполните массовую вставку

Вы можете проверить эту статью подробнее: массовая вставка данных с помощью C# DataTable и SQL Server OpenXML функция

Но он не протестирован с 2 миллионами записей, он будет использовать только память на машине, так как вам нужно загрузить 2 миллиона записей и вставить ее.

Решение проблемы для SqlBulkCopy:

Я использовал StreamReader для преобразования и обработки текстового файла. Результатом стал список моего объекта.

Я создал класс, чем занимает Datatable или List<T> и размер буфера (CommitBatchSize). Он преобразует список в таблицу данных, используя расширение (во втором классе).

Работает очень быстро. На моем компьютере я могу вставить более 10 миллионов сложных записей менее чем за 10 секунд.

Вот класс:

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);
}

Я использую утилиту BCP. (Программа массового копирования). Я загружаю около 1,5 миллионов текстовых записей каждый месяц. Каждая текстовая запись имеет ширину 800 символов. На моем сервере добавление 1,5 миллиона текстовых записей в таблицу SQL Server занимает около 30 секунд.

Инструкции для bcp находятся по http://msdn.microsoft.com/en-us/library/ms162802.aspx

Я попытался с этим методом, и это значительно сократило время выполнения моей вставки базы данных.

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

foreach (traverse your loop here)
{
      toinsert.Add(string.Format("( '{0}', '{1}', '{2}' )", "Val1", "Val2", "Val3"));
}
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.

Недавно я столкнулся с этим сценарием (более 7 миллионов строк) и стал использовать sqlcmd через powershell (после синтаксического анализа необработанных данных в операторах вставки SQL) в сегментах по 5000 одновременно (SQL не может обработать 7 миллионов строк в одном объединенном задании или даже 500 000 строк в этом отношении, если только он не разбит на более мелкие части по 5K. Затем вы можете запускать каждый сценарий 5K один за другим.) По мере необходимости я использовал новую команду последовательности в SQL Server 2012 Enterprise. Я не мог найти программный способ быстро и эффективно вставить семь миллионов строк данных с помощью указанной команды последовательности.

Во-вторых, одна из вещей, на которые следует обратить внимание при вставке миллиона строк или более данных за один сеанс, - это потребление ресурсов процессора и памяти (в основном памяти) во время процесса вставки. SQL потребляет память / ЦП с такой работой, не выпуская упомянутые процессы. Само собой разумеется, что если у вас недостаточно вычислительной мощности или памяти на сервере, вы можете довольно легко его сломать за короткое время (что я выяснил нелегко). Если вы дошли до того, что потребление памяти превышает 70-75%, просто перезагрузите сервер, и процессы вернутся в нормальное состояние.

Мне пришлось выполнить несколько проб и ошибок, чтобы посмотреть, каковы ограничения для моего сервера (учитывая ограниченные ресурсы ЦП / памяти для работы), прежде чем я смог получить окончательный план выполнения. Я бы посоветовал вам сделать то же самое в тестовой среде, прежде чем внедрять это в производство.

Я столкнулся с проблемой решения, которое должно работать с ADO, Entity и Dapper, поэтому я сделал эту библиотеку ; он генерирует партии в виде:

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

эта ссылка содержит инструкции. Это безопасно от SQL-инъекций, потому что вместо конкатенации используются параметры; вы также можете включить вставку идентификатора, если это необходимо, с помощью необязательного параметра.

Использование с 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();
        }
    }
}

Использование с Даппером:

      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);
    }
}

Использование с Entity Framework:

      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);
}

Другой способ: если текстовые данные находятся в формате Json, вы можете использовать метод OPENJSON в SQL Server для массовой вставки. Я проверил на своем локальном компьютере и смог вставить один миллион записей за 51 секунду.

Это хранимая процедура:

      CREATE PROCEDURE sp_upsert_play_user_details1  
(  
@array VARCHAR(MAX)  
)  
AS  
BEGIN  
BEGIN TRY  
BEGIN TRANSACTION  
  
 INSERT INTO tbl_play_user_details    
 (vc_first_name, vc_last_name, vc_full_name, vc_gender, vc_phone_number, vc_email, vc_pet, vc_vehicle_model, vc_vehicle_no, int_created_on, int_created_by)    

 SELECT firstName, lastName, fullName, gender, phoneNumber, email, pet, vehicle, vehicleNumber, GETDATE(), createdBy  FROM OPENJSON(@array)  
        WITH (  firstName VARCHAR(100),  
    lastName VARCHAR(100),  
    fullName VARCHAR(100),  
    gender VARCHAR(100),  
    phoneNumber VARCHAR(100),  
    email VARCHAR(100),  
    pet VARCHAR(100),  
    vehicle VARCHAR(100),  
    vehicleNumber VARCHAR(100),  
                createdBy int);  
  
COMMIT TRANSACTION  
END TRY  
BEGIN CATCH  
ROLLBACK TRANSACTION                            
DECLARE @ErrorMessage NVARCHAR(4000)=ERROR_MESSAGE()+' Please verify "'+ERROR_PROCEDURE()+'" stored procedure at the line number '+CONVERT(NVARCHAR(20),ERROR_LINE() )+ '.';                                      
DECLARE @ErrorSeverity INT = ERROR_SEVERITY();                                      
DECLARE @ErrorState INT=ERROR_STATE();                                      
RAISERROR (@ErrorMessage,@ErrorSeverity,@ErrorState)   
END CATCH  
END  
GO

Пример JSON для тестирования:

      DECLARE @array VARCHAR(MAX);  
SET @array = '[{  
        "firstName": "Winston",  
        "lastName": "Lemke",  
        "fullName": "Winston Lemke",  
        "gender": "Male",  
        "phoneNumber": "466.780.4652 x268",  
        "email": "Josefa89@yahoo.com",  
        "pet": "Villanuco de Las Encartaciones",  
        "vehicle": "Mazda Escalade",  
        "vehicleNumber": "8CP7UC1N83MY25770",  
        "createdBy": 1  
    },  
 {  
        "firstName": "Finn",  
        "lastName": "Bartoletti",  
        "fullName": "Finn Bartoletti",  
        "gender": "Female",  
        "phoneNumber": "1-931-498-0214 x454",  
        "email": "Clair.Rodriguez@hotmail.com",  
        "pet": "Bouvier des Flandres",  
        "vehicle": "Tesla Ranchero",  
        "vehicleNumber": "MG1XVY29D0M798471",  
        "createdBy": 1  
    }]';  

EXEC sp_upsert_play_user_details1 @array;

В С# я проанализировал данные из локального файла и передал строку хранимой процедуре:

      string text = System.IO.File.ReadAllText(@"C:\Users\krish\OneDrive\Desktop\object.json");

_playdb.CommandTimeout = 3000;
_playdb.sp_upsert_play_user_details1(text);

Как упоминалось выше, для вставки записей в один миллион потребовалось всего 51 секунда и, вероятно, намного быстрее на более быстром сервере / рабочей машине.

SqlBulkCopy это хорошо.

но вы должны подготовить Данные не являются проблемой, например, PK\FK не конфликтует, поэтому вставить 200k, записанные в SQL Server, очень просто.

Другие вопросы по тегам