Массовая вставка лучшего способа об этом?+ Помочь мне полностью понять, что я нашел до сих пор - PullRequest
7 голосов
/ 20 мая 2010

Итак, я увидел этот пост здесь и прочитал его, и похоже, что массовое копирование может быть подходящим вариантом.

Каков наилучший способ массовой вставки базы данных из c #?

У меня все еще есть вопросы, и я хочу знать, как все это работает.

Итак, я нашел 2 урока.

http://www.codeproject.com/KB/cs/MultipleInsertsIn1dbTrip.aspx#_Toc196622241

http://www.codeproject.com/KB/linq/BulkOperations_LinqToSQL.aspx

Первый способ использует 2 функции ado.net 2.0. BulkInsert и BulkCopy. второй использует linq to sql и OpenXML.

Этот вид мне нравится, так как я уже использую linq для sql и предпочитаю его над ado.net. Однако, как один человек указал в сообщениях, что он просто обходит проблему за счет производительности (на мой взгляд, в этом нет ничего плохого)

Сначала я расскажу о двух способах в первом уроке

Я использую VS2010 Express (для тестирования учебников я использовал VS2008 и не уверен, какую версию .net я только что загрузил туда образцы файлов и запустил их), .net 4.0, MVC 2.0, SQl Server 2005

  1. Является ли ado.net 2.0 самой последней версией?
  2. Исходя из технологии, которую я использую, есть ли какие-то обновления к тому, что я собираюсь показать, которые могли бы как-то улучшить это?
  3. Есть ли что-то, о чем я не должен знать этот учебник?

BulkInsert

Я использую эту таблицу для всех примеров.

CREATE TABLE [dbo].[TBL_TEST_TEST]
(
    ID INT IDENTITY(1,1) PRIMARY KEY,
    [NAME] [varchar](50) 
)

Код SP

USE [Test]
GO
/****** Object:  StoredProcedure [dbo].[sp_BatchInsert]    Script Date: 05/19/2010 15:12:47 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_BatchInsert] (@Name VARCHAR(50) )
AS
BEGIN
            INSERT INTO TBL_TEST_TEST VALUES (@Name);
END 

C # код

/// <summary>
/// Another ado.net 2.0 way that uses a stored procedure to do a bulk insert.
/// Seems slower then "BatchBulkCopy" way and it crashes when you try to insert 500,000 records in one go.
/// http://www.codeproject.com/KB/cs/MultipleInsertsIn1dbTrip.aspx#_Toc196622241
/// </summary>
private static void BatchInsert()
{
    // Get the DataTable with Rows State as RowState.Added
    DataTable dtInsertRows = GetDataTable();

    SqlConnection connection = new SqlConnection(connectionString);
    SqlCommand command = new SqlCommand("sp_BatchInsert", connection);
    command.CommandType = CommandType.StoredProcedure;
    command.UpdatedRowSource = UpdateRowSource.None;

    // Set the Parameter with appropriate Source Column Name
    command.Parameters.Add("@Name", SqlDbType.VarChar, 50, dtInsertRows.Columns[0].ColumnName);

    SqlDataAdapter adpt = new SqlDataAdapter();
    adpt.InsertCommand = command;
    // Specify the number of records to be Inserted/Updated in one go. Default is 1.
    adpt.UpdateBatchSize = 1000;

    connection.Open();
    int recordsInserted = adpt.Update(dtInsertRows);
    connection.Close();
}

Итак, во-первых, размер партии. Почему вы устанавливаете размер пакета на что-либо, кроме количества отправляемых вами записей? Как будто я отправляю 500 000 записей, так что я сделал размер партии 500 000.

Далее, почему он падает, когда я это делаю? Если я установлю его на 1000 для размера партии, он будет работать нормально.

System.Data.SqlClient.SqlException was unhandled
  Message="A transport-level error has occurred when sending the request to the server. (provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.)"
  Source=".Net SqlClient Data Provider"
  ErrorCode=-2146232060
  Class=20
  LineNumber=0
  Number=233
  Server=""
  State=0
  StackTrace:
       at System.Data.Common.DbDataAdapter.UpdatedRowStatusErrors(RowUpdatedEventArgs rowUpdatedEvent, BatchCommandInfo[] batchCommands, Int32 commandCount)
       at System.Data.Common.DbDataAdapter.UpdatedRowStatus(RowUpdatedEventArgs rowUpdatedEvent, BatchCommandInfo[] batchCommands, Int32 commandCount)
       at System.Data.Common.DbDataAdapter.Update(DataRow[] dataRows, DataTableMapping tableMapping)
       at System.Data.Common.DbDataAdapter.UpdateFromDataTable(DataTable dataTable, DataTableMapping tableMapping)
       at System.Data.Common.DbDataAdapter.Update(DataTable dataTable)
       at TestIQueryable.Program.BatchInsert() in C:\Users\a\Downloads\TestIQueryable\TestIQueryable\TestIQueryable\Program.cs:line 124
       at TestIQueryable.Program.Main(String[] args) in C:\Users\a\Downloads\TestIQueryable\TestIQueryable\TestIQueryable\Program.cs:line 16
  InnerException: 

Время, необходимое для вставки 500 000 записей с размером пакета вставки 1000, заняло "2 минуты и 54 секунды"

Конечно, это не официальное время, когда я сидел там с секундомером (я уверен, что есть лучшие способы, но было слишком лениво, чтобы посмотреть, что они где)

Так что я нахожу это немного медленным по сравнению со всеми моими другими (ожидаю, что linq to sql вставит один), и я не совсем уверен, почему.

Затем я посмотрел на тёмную копию

/// <summary>
/// An ado.net 2.0 way to mass insert records. This seems to be the fastest.
/// http://www.codeproject.com/KB/cs/MultipleInsertsIn1dbTrip.aspx#_Toc196622241
/// </summary>
private static void BatchBulkCopy()
{
    // Get the DataTable 
    DataTable dtInsertRows = GetDataTable();

    using (SqlBulkCopy sbc = new SqlBulkCopy(connectionString, SqlBulkCopyOptions.KeepIdentity))
    {
        sbc.DestinationTableName = "TBL_TEST_TEST";

        // Number of records to be processed in one go
        sbc.BatchSize = 500000;

        // Map the Source Column from DataTabel to the Destination Columns in SQL Server 2005 Person Table
        // sbc.ColumnMappings.Add("ID", "ID");
        sbc.ColumnMappings.Add("NAME", "NAME");

        // Number of records after which client has to be notified about its status
        sbc.NotifyAfter = dtInsertRows.Rows.Count;

        // Event that gets fired when NotifyAfter number of records are processed.
        sbc.SqlRowsCopied += new SqlRowsCopiedEventHandler(sbc_SqlRowsCopied);

        // Finally write to server
        sbc.WriteToServer(dtInsertRows);
        sbc.Close();
    }

}

Похоже, что он работал очень быстро и даже не нуждался в SP (можете ли вы использовать SP с массовым копированием? Если вы можете, было бы лучше?)

У BatchCopy не было проблем с размером пакета 500 000. И снова зачем уменьшать количество записей, которое вы хотите отправить?

Я обнаружил, что с BatchCopy и размером 500 000 пакетов для выполнения потребовалось всего 5 секунд . Затем я попытался с размером партии 1000, и это заняло всего 8 секунд .

Намного быстрее, чем наверху.

Теперь я попробовал другой урок.

USE [Test]
GO
/****** Object:  StoredProcedure [dbo].[spTEST_InsertXMLTEST_TEST]    Script Date: 05/19/2010 15:39:03 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spTEST_InsertXMLTEST_TEST](@UpdatedProdData nText)
AS 
 DECLARE @hDoc int   

 exec sp_xml_preparedocument @hDoc OUTPUT,@UpdatedProdData 

 INSERT INTO TBL_TEST_TEST(NAME)
 SELECT XMLProdTable.NAME
    FROM OPENXML(@hDoc, 'ArrayOfTBL_TEST_TEST/TBL_TEST_TEST', 2)   
       WITH (
                ID Int,                 
                NAME varchar(100)
            ) XMLProdTable

EXEC sp_xml_removedocument @hDoc

C # код.

/// <summary>
/// This is using linq to sql to make the table objects. 
/// It is then serailzed to to an xml document and sent to a stored proedure
/// that then does a bulk insert(I think with OpenXML)
///  http://www.codeproject.com/KB/linq/BulkOperations_LinqToSQL.aspx
/// </summary>
private static void LinqInsertXMLBatch()
{
    using (TestDataContext db = new TestDataContext())
    {
        TBL_TEST_TEST[] testRecords = new TBL_TEST_TEST[500000];
        for (int count = 0; count < 500000; count++)
        {
            TBL_TEST_TEST testRecord = new TBL_TEST_TEST();
            testRecord.NAME = "Name : " + count;
            testRecords[count] = testRecord;
        }

        StringBuilder sBuilder = new StringBuilder();
        System.IO.StringWriter sWriter = new System.IO.StringWriter(sBuilder);
        XmlSerializer serializer = new XmlSerializer(typeof(TBL_TEST_TEST[]));
        serializer.Serialize(sWriter, testRecords);
        db.insertTestData(sBuilder.ToString());
    }
}

Так что мне это нравится, потому что я могу использовать объекты, хотя это немного избыточно. Я не понимаю, как работает SP. Как будто я не понимаю всего этого. Я не знаю, есть ли у OPENXML какая-то пакетная вставка под капотом, но я даже не знаю, как взять этот пример SP и изменить его под мои таблицы, поскольку, как я уже сказал, я не знаю, что происходит.

Я также не знаю, что произойдет, если в объекте у вас будет больше таблиц. Например, у меня есть таблица ProductName, которая имеет отношение к таблице Product или что-то в этом роде.

В linq to sql вы можете получить объект имени продукта и внести изменения в таблицу Product в этом же объекте. Поэтому я не уверен, как принять это во внимание. Я не уверен, что мне придется делать отдельные вставки или что.

Время было достаточно хорошим для 500 000 записей: 52 секунд

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

/// <summary>
/// This is using linq to sql to to insert lots of records. 
/// This way is slow as it uses no mass insert.
/// Only tried to insert 50,000 records as I did not want to sit around till it did 500,000 records.
/// http://www.codeproject.com/KB/linq/BulkOperations_LinqToSQL.aspx
/// </summary>
private static void LinqInsertAll()
{
    using (TestDataContext db = new TestDataContext())
    {
        db.CommandTimeout = 600;
        for (int count = 0; count < 50000; count++)
        {
            TBL_TEST_TEST testRecord = new TBL_TEST_TEST();
            testRecord.NAME = "Name : " + count;
            db.TBL_TEST_TESTs.InsertOnSubmit(testRecord);
        }
        db.SubmitChanges();
    }
}

Я сделал только 50 000 записей, и это заняло больше минуты.

Так что я действительно сузил это до способа массовой вставки или массового копирования linq to sql. Я просто не уверен, как это сделать, когда у вас есть отношения в любом случае. Я не уверен, как они встают при обновлении вместо вставок, так как я еще не удосужился попробовать.

Не думаю, что мне когда-либо понадобится вставлять / обновлять более 50 000 записей одного типа, но в то же время я знаю, что мне придется проверять записи перед их вставкой, чтобы это замедляло их и делает linq to sql лучше, чем ваши полученные объекты, особенно если вы сначала разбираете данные из файла xml перед тем, как вставить их в базу данных.

Полный код C #

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Data;
using System.Data.SqlClient;

namespace TestIQueryable
{
    class Program
    {
        private static string connectionString = "";
        static void Main(string[] args)
        {
            BatchInsert();
            Console.WriteLine("done");
        }

        /// <summary>
        /// This is using linq to sql to to insert lots of records. 
        /// This way is slow as it uses no mass insert.
        /// Only tried to insert 50,000 records as I did not want to sit around till it did 500,000 records.
        /// http://www.codeproject.com/KB/linq/BulkOperations_LinqToSQL.aspx
        /// </summary>
        private static void LinqInsertAll()
        {
            using (TestDataContext db = new TestDataContext())
            {
                db.CommandTimeout = 600;
                for (int count = 0; count < 50000; count++)
                {
                    TBL_TEST_TEST testRecord = new TBL_TEST_TEST();
                    testRecord.NAME = "Name : " + count;
                    db.TBL_TEST_TESTs.InsertOnSubmit(testRecord);
                }
                db.SubmitChanges();
            }
        }

        /// <summary>
        /// This is using linq to sql to make the table objects. 
        /// It is then serailzed to to an xml document and sent to a stored proedure
        /// that then does a bulk insert(I think with OpenXML)
        ///  http://www.codeproject.com/KB/linq/BulkOperations_LinqToSQL.aspx
        /// </summary>
        private static void LinqInsertXMLBatch()
        {
            using (TestDataContext db = new TestDataContext())
            {
                TBL_TEST_TEST[] testRecords = new TBL_TEST_TEST[500000];
                for (int count = 0; count < 500000; count++)
                {
                    TBL_TEST_TEST testRecord = new TBL_TEST_TEST();
                    testRecord.NAME = "Name : " + count;
                    testRecords[count] = testRecord;
                }

                StringBuilder sBuilder = new StringBuilder();
                System.IO.StringWriter sWriter = new System.IO.StringWriter(sBuilder);
                XmlSerializer serializer = new XmlSerializer(typeof(TBL_TEST_TEST[]));
                serializer.Serialize(sWriter, testRecords);
                db.insertTestData(sBuilder.ToString());
            }
        }

        /// <summary>
        /// An ado.net 2.0 way to mass insert records. This seems to be the fastest.
        /// http://www.codeproject.com/KB/cs/MultipleInsertsIn1dbTrip.aspx#_Toc196622241
        /// </summary>
        private static void BatchBulkCopy()
        {
            // Get the DataTable 
            DataTable dtInsertRows = GetDataTable();

            using (SqlBulkCopy sbc = new SqlBulkCopy(connectionString, SqlBulkCopyOptions.KeepIdentity))
            {
                sbc.DestinationTableName = "TBL_TEST_TEST";

                // Number of records to be processed in one go
                sbc.BatchSize = 500000;

                // Map the Source Column from DataTabel to the Destination Columns in SQL Server 2005 Person Table
                // sbc.ColumnMappings.Add("ID", "ID");
                sbc.ColumnMappings.Add("NAME", "NAME");

                // Number of records after which client has to be notified about its status
                sbc.NotifyAfter = dtInsertRows.Rows.Count;

                // Event that gets fired when NotifyAfter number of records are processed.
                sbc.SqlRowsCopied += new SqlRowsCopiedEventHandler(sbc_SqlRowsCopied);

                // Finally write to server
                sbc.WriteToServer(dtInsertRows);
                sbc.Close();
            }

        }


        /// <summary>
        /// Another ado.net 2.0 way that uses a stored procedure to do a bulk insert.
        /// Seems slower then "BatchBulkCopy" way and it crashes when you try to insert 500,000 records in one go.
        /// http://www.codeproject.com/KB/cs/MultipleInsertsIn1dbTrip.aspx#_Toc196622241
        /// </summary>
        private static void BatchInsert()
        {
            // Get the DataTable with Rows State as RowState.Added
            DataTable dtInsertRows = GetDataTable();

            SqlConnection connection = new SqlConnection(connectionString);
            SqlCommand command = new SqlCommand("sp_BatchInsert", connection);
            command.CommandType = CommandType.StoredProcedure;
            command.UpdatedRowSource = UpdateRowSource.None;

            // Set the Parameter with appropriate Source Column Name
            command.Parameters.Add("@Name", SqlDbType.VarChar, 50, dtInsertRows.Columns[0].ColumnName);

            SqlDataAdapter adpt = new SqlDataAdapter();
            adpt.InsertCommand = command;
            // Specify the number of records to be Inserted/Updated in one go. Default is 1.
            adpt.UpdateBatchSize = 500000;

            connection.Open();
            int recordsInserted = adpt.Update(dtInsertRows);
            connection.Close();
        }



        private static DataTable GetDataTable()
        {
            // You First need a DataTable and have all the insert values in it
            DataTable dtInsertRows = new DataTable();
            dtInsertRows.Columns.Add("NAME");

            for (int i = 0; i < 500000; i++)
            {
                DataRow drInsertRow = dtInsertRows.NewRow();
                string name = "Name : " + i;
                drInsertRow["NAME"] = name;
                dtInsertRows.Rows.Add(drInsertRow);


            }
            return dtInsertRows;

        }


        static void sbc_SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
        {
            Console.WriteLine("Number of records affected : " + e.RowsCopied.ToString());
        }


    }
}

Ответы [ 3 ]

2 голосов
/ 20 мая 2010

Размер пакета предназначен для уменьшения влияния задержки сети. Это не должно быть больше, чем несколько тысяч. Несколько операторов собираются вместе и отправляются как единое целое, поэтому вы получаете одно сетевое отключение один раз за каждое N операторов, а не один раз за оператор.

0 голосов
/ 23 октября 2013

О, я рад, что мы не единственные, кто страдает от этой InsertOnSubmit () проблемы.

Недавно наша компания перенесла центры обработки данных, а затем наши SQL Server машины находились за 6 000 миль, а не находились в одной стране.

И внезапно сохранение 1800 записей заняло 3,5 минуты, а не 3-4 секунды. Наши пользователи не были счастливы !!

Решением было заменить InsertOnSubmit вызовами на библиотеку массовой вставки.

Прочитайте раздел «Вставка записей с помощью массовой вставки» на этой странице. Он показывает (очень мало) изменений, которые вам действительно нужно внести в свой код, чтобы обойти эту проблему задержки.

Вам просто нужно добавить три строки кода и использовать пару классов C #, представленных на этой странице.

http://mikesknowledgebase.com/pages/LINQ/InsertAndDeletes.htm

0 голосов
/ 20 мая 2010

Это одноразовая массовая копия или обычная вещь?

Если это одноразовый или, например, только один раз в день, используйте BCP, это намного быстрее, поскольку он использует специальные API, которые работают быстрее, чем ado.net.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...