Więc zobaczyłem ten post tutaj i przeczytałem go i wygląda na to, że kopiowanie masowe może być drogą do zrobienia.Wstawianie zbiorcze najlepiej na ten temat? + Pomagając mi zrozumieć w pełni to, co znalazłem do tej pory
What’s the best way to bulk database inserts from c#?
Mam jeszcze kilka pytań, ale chcę wiedzieć, jak rzeczy faktycznie pracują.
Więc znalazłem 2 samouczki.
http://www.codeproject.com/KB/cs/MultipleInsertsIn1dbTrip.aspx#_Toc196622241
http://www.codeproject.com/KB/linq/BulkOperations_LinqToSQL.aspx
Pierwszy sposób wykorzystuje 2 ADO.NET 2.0 funkcje. BulkInsert i BulkCopy. drugi używa linq do sql i OpenXML.
Ten rodzaj odwołania do mnie, ponieważ używam linq do sql już i wolę go ponad ado.net. Jednak jak jedna osoba wskazała na stanowiskach co właśnie dzieje się wokół kwestii kosztem wydajności (w tym nic złego, że moim zdaniem)
Najpierw będę mówić o 2 sposoby w pierwszym tutorialu
Używam VS2010 Express (do testowania tutoriali użyłem VS2008 i nie wiesz jaka wersja .net właśnie załadowany tam przykładowe pliki i prowadził je), .NET 4.0, MVC 2.0, SQL Server 2005
- Czy ado.net 2.0 najnowsza wersja?
- W oparciu o technologię, której używam, czy są jakieś aktualizacje tego, co zamierzam pokazać, aby jakoś ją poprawić?
- Czy jest coś, czego te lekcje pominięto, o których powinienem wiedzieć?
BulkInsert
Używam tej tabeli dla wszystkich przykładów.
CREATE TABLE [dbo].[TBL_TEST_TEST]
(
ID INT IDENTITY(1,1) PRIMARY KEY,
[NAME] [varchar](50)
)
Kod 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# Code
/// <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();
}
Więc pierwszą rzeczą jest wielkość partii. Dlaczego ustawiasz wielkość partii na inną niż liczba wysyłanych rekordów? Tak jak wysyłam 500 000 rekordów, więc zrobiłem partię o wielkości 500 000.
Dalej dlaczego to się zawiesza, kiedy to robię? Jeśli ustawię go na 1000 dla wielkości partii, działa dobrze.
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:
czasu zajęło, aby wstawić 500000 rekordy z rozmiarem wkładki wsadowym 1000 miały „2 minut i 54 sekund”
Oczywiście nie jest to oficjalny czas Siedziałem tam z stopera (I Jestem pewien, że istnieją lepsze sposoby, ale był zbyt leniwy, aby spojrzeć na to, co mają gdzie)
Więc uważam, że trochę powolne w porównaniu do wszystkich innych (oczekuj linq do sql wstawić jeden) i nie jestem naprawdę pewien dlaczego.
Następny Spojrzałem na bulkcopy
/// <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();
}
}
Ten wydawało się bardzo szybko i nawet nie potrzebują SP (można użyć SP z luzem kopię? Jeśli można byłoby lepiej?)
BatchCopy nie miał problemu z wielkością 500 000 sztuk. Więc ponownie, dlaczego jest mniejsza niż liczba rekordów, które chcesz wysłać?
Znalazłem, że za pomocą BatchCopy i 500 000 partii wymagało to tylko 5 sekund. Następnie próbowałem z partią o wielkości 1000 i zajęło to tylko 8 sekund.
O wiele szybciej niż bulkinsert powyżej.
Teraz wypróbowałem drugi samouczek.
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
Kod 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());
}
}
Podoba mi się to, ponieważ używam obiektów, mimo że jest to zbyteczne. Nie rozumiem, jak działa SP. Jakbym nie zrozumiał tego. Nie wiem, czy OPENXML ma kilka wsadowych wstawek pod maską, ale nie wiem nawet, jak wziąć ten przykład SP i zmienić go tak, by pasował do moich tabel, ponieważ tak jak powiedziałem, nie wiem, co się dzieje.
Nie wiem też, co by się stało, gdyby obiekt zawierał więcej tabel. Tak jak mówię, że mam tabelę ProductName, co ma związek z tabelą produktów lub coś podobnego.
W linq do sql można uzyskać obiekt nazwy produktu i wprowadzić zmiany w tabeli produktów w tym samym obiekcie. Nie wiem, jak to uwzględnić. Nie jestem pewien, czy musiałbym zrobić osobne wkładki, czy co.
Czas było całkiem dobre dla 500.000 zapisów zajęło 52 sekund
ostatni sposób oczywiście był tylko przy użyciu LINQ zrobić to wszystko i to było całkiem źle.
/// <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();
}
}
Zrobiłem tylko 50 000 rekordów i zajęło mi to minutę.
Naprawdę zminimalizowałem to zrobić z linq do sql luzem wstawić drogę lub masową kopię. Po prostu nie jestem pewien, jak to zrobić, gdy masz związek w obie strony. Nie jestem pewien, jak obaj się podnoszą, robiąc aktualizacje zamiast wstawek, ponieważ nie mam okazji, aby spróbować jeszcze.
Nie sądzę, że kiedykolwiek będę musiał wstawić/zaktualizować ponad 50 000 rekordów w jednym typie, ale jednocześnie wiem, że będę musiał dokonać sprawdzania poprawności rekordów przed wstawieniem, aby spowolnić to i ten rodzaj sprawia, że linq do sql ładniejszy jak twoje obiekty, zwłaszcza jeśli twoje pierwsze parsowanie danych z pliku xml przed wstawić do bazy danych.
Pełny kod 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());
}
}
}
Więc co będzie prefekt wielkość partii w głowie wtedy? Tam, gdzie nie wpływasz na opóźnienia sieci, ale nie robisz 10.000 podróży. – chobo2
Chcesz spróbować uniknąć opóźnień sieci, tj. Maksymalnie zredukować wpływ opóźnień. porcjowanie 1000-10000 jest ogólnie rzecz biorąc w porządku, ale zależy to od tego, ile masz latencji w sieci i ile pamięci możesz sobie pozwolić na użycie w instrukcjach grupowania. Na przykład. jeśli masz N instrukcji i opóźnienie Lms, całkowite opóźnienie wynosi N * L ms. Jeśli partia jest w rozmiarze B, to opóźnienie wynosi N * L/B, więc zmniejsza się opóźnienie o wielkość partii. Zmodyfikuj te zmienne, aby uzyskać całkowite opóźnienie akceptowalne w Twojej sytuacji - np. mały procent czasu wykonania zapytania. – mdma
Czy masz na myśli, że wielkość partii nie ma wpływu na rejestr transakcji i nie jest zatwierdzana? –