2010-09-20 11 views
8

Ich füge große Anzahl von Datensätzen mit LinqToSql von C# zu SqlServer 2008 Express DB. Es sieht so aus, als wäre die Einfügung sehr langsam. Im Folgenden ist das Code-Snippet.Sehr langsam einfügen Prozess mit Linq zu Sql

public void InsertData(int id) 
{ 

    MyDataContext dc = new MyDataContext(); 

    List<Item> result = GetItems(id); 

    foreach (var item in result) 
    { 
    DbItem dbItem = new DbItem(){ItemNo = item.No, ItemName=item.Name}; 
    dc.Items.InsertOnSubmit(); 
    } 

    dc.SubmitChanges(); 
} 

Mache ich etwas falsch? Oder die Verwendung von Linq zum Einfügen einer großen Anzahl von Datensätzen ist eine schlechte Wahl?

Update: Danke für alle Antworten. @ p.campbell: Sorry für die Datensätze zählen, es war ein Tippfehler, tatsächlich ist es rund 100000. Datensätze reichen auch bis 200k.

Wie alle Vorschläge habe ich diese Operation in Teile (auch eine Änderung der Anforderung und Design-Entscheidung) und Abrufen von Daten in kleinen Stücken und Einfügen in die Datenbank wie und wann es kommt. Ich habe diese InsertData() -Methode in Thread-Operation und jetzt SmartThreadPool zum Erstellen eines Pools von 25 Threads für die gleiche Operation verwenden. In diesem Szenario füge ich jeweils nur 100 Datensätze ein. Jetzt, als ich das mit Linq oder SQL-Abfrage versuchte, machte es keinen Unterschied in Bezug auf die benötigte Zeit.

Gemäß meiner Anforderung wird dieser Vorgang geplant, jede Stunde ausgeführt und Datensätze für ca. 4k-6k Benutzer abgerufen. So, jetzt bin ich Pooling alle Benutzerdaten (Abrufen und Einfügen in DB) Operation als eine Aufgabe und zugeordnet zu einem Thread. Jetzt dauert dieser gesamte Prozess ungefähr 45 Minuten für ungefähr 250.000 Datensätze.

Gibt es einen besseren Weg, diese Art von Aufgabe zu erledigen? Oder kann mir jemand vorschlagen, wie ich diesen Prozess verbessern kann?

+1

Wie viele Datensätze und wie lange dauert der Vorgang? Welche Datentypen werden hier verwendet? –

+0

Mehr als 1000000 Datensätze und meist String-Datentypen, aber nicht mehr als 10 Felder. – JPReddy

+1

Eine Million Einfügungen brauchen Zeit, egal was. Ich vermute, wenn Sie die generierten SQL-Anweisungen, alle 1 Million davon, kopieren und ad-hoc ausführen, sehen Sie keinen großen Unterschied zu Management Studio! –

Antwort

11

Zum Einsetzen enorme Menge an Daten in SQL in einem oner

Linq oder SqlCommand, neither are designed for bulk copying data into SQL.

Sie können die SqlBulkCopy class verwenden, die verwalteten Zugriff auf das Dienstprogramm bcp zum Massenladen von Daten in Sql aus so ziemlich jeder Datenquelle bietet.

Die SqlBulkCopy-Klasse kann verwendet werden, um Daten nur in SQL Server-Tabellen zu schreiben. Die Datenquelle ist jedoch nicht auf SQL Server beschränkt. Jede Datenquelle kann verwendet werden, solange die Daten in eine DataTable-Instanz geladen oder mit einer IDataReader-Instanz gelesen werden können.

Leistungsvergleich

SqlBulkCopy ist bei weitem der schnellste, auch wenn Daten aus einer einfachen CSV-Datei zu laden.

Linq wird nur eine Ladung von Insert Anweisungen in SQL generieren und sie an Ihren SQL Server senden. Dies ist nicht anders als bei Ad-hoc-Abfragen mit SqlCommand. Die Leistung von SqlCommand gegenüber Linq ist praktisch identisch.

Der Beweis

(SQL Express 2008, .Net 4,0)

SqlBulkCopy

SqlBulkCopy Mit 100.000 Zeilen aus einer CSV-Datei laden (einschließlich Laden der Daten)

using (SqlConnection conn = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=EffectCatalogue;Data Source=.\\SQLEXPRESS;")) 
{ 
    conn.Open(); 
    Stopwatch watch = Stopwatch.StartNew(); 

    string csvConnString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\data\\;Extended Properties='text;'"; 
    OleDbDataAdapter oleda = new OleDbDataAdapter("SELECT * FROM [test.csv]", csvConnString); 
    DataTable dt = new DataTable(); 
    oleda.Fill(dt); 

    using (SqlBulkCopy copy = new SqlBulkCopy(conn)) 
    { 
     copy.ColumnMappings.Add(0, 1); 
     copy.ColumnMappings.Add(1, 2); 
     copy.DestinationTableName = "dbo.Users"; 
     copy.WriteToServer(dt); 
    } 
    Console.WriteLine("SqlBulkCopy: {0}", watch.Elapsed); 
} 

SqlCommand

using (SqlConnection conn = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestDb;Data Source=.\\SQLEXPRESS;")) 
{ 
    conn.Open(); 
    Stopwatch watch = Stopwatch.StartNew(); 
    SqlCommand comm = new SqlCommand("INSERT INTO Users (UserName, [Password]) VALUES ('Simon', 'Password')", conn); 
    for (int i = 0; i < 100000; i++) 
    { 
     comm.ExecuteNonQuery(); 
    } 
    Console.WriteLine("SqlCommand: {0}", watch.Elapsed); 
} 

LinqToSql

using (SqlConnection conn = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestDb;Data Source=.\\SQLEXPRESS;")) 
{ 
    conn.Open(); 
    Stopwatch watch = Stopwatch.StartNew(); 
    EffectCatalogueDataContext db = new EffectCatalogueDataContext(conn); 
    for (int i = 0; i < 100000; i++) 
    { 
     User u = new User(); 
     u.UserName = "Simon"; 
     u.Password = "Password"; 
     db.Users.InsertOnSubmit(u); 
    } 
    db.SubmitChanges(); 
    Console.WriteLine("Linq: {0}", watch.Elapsed); 
} 

Ergebnisse

SqlBulkCopy: 00:00:02.90704339 
SqlCommand: 00:00:50.4230604 
Linq: 00:00:48.7702995 
+1

Diese Leistung ist wirklich hervorragend. Danke für diesen Vorschlag, ich benutze das jetzt. – JPReddy

3

Wenn Sie große Datensätze einfügen, können Sie mit BULK INSERT versuchen.

Nach meinem Wissen gibt es keine Entsprechung von Masseneinfügung in Linq zu SQL.

3

Sie haben die SubmitChanges() einmal aufgerufen, was gut ist. Dies bedeutet, dass nur eine Verbindung und Transaktion verwendet werden.

Erwägen Sie, Ihren Code zu refactorieren, um InsertAllOnSubmit() stattdessen zu verwenden.

Die INSERT-Anweisungen werden eins nach dem anderen gesendet, aber vielleicht ist das besser lesbar?

Einige andere Dinge zu fragen/betrachten:

  • Was auf der Zieltabelle der Zustand des Indizes ist? Zu viele werden die Schreibvorgänge verlangsamen. * Ist die Datenbank im einfachen oder vollständigen Wiederherstellungsmodell?
  • Erfassen Sie die SQL-Anweisungen über die Leitung. Wiederholen Sie diese Anweisungen in einer Ad-hoc-Abfrage für Ihre SQL Server-Datenbank. Mir ist klar, dass Sie SQL Express verwenden und wahrscheinlich keinen SQL Profiler haben. Verwenden Sie context.Log = Console.Out; zu output your LINQ To SQL statements to the console. Bevorzugen Sie jedoch SQL Profiler für die Bequemlichkeit.
  • Führen die erfaßten SQL-Anweisungen dasselbe wie Ihr Clientcode aus? Wenn ja, dann ist das Perf-Problem auf der Datenbankseite.
+0

Wie funktioniert das intern? – cjk

+0

Danke für die Eingabe. Refactoring ist getan. Nicht viel Verbesserung.Keine anderen Indizes als das ID-Feld, das der Primärschlüssel ist und automatisch generiert wird. – JPReddy

+0

@JPReddy: gute Sachen. Wäre interessiert, die perf der + adhoc SQL-Anweisungen zu sehen. –

1

Hier ist ein schöner Spaziergang-durch, wie eine Masse-Insert-Klasse zu Ihrer Anwendung hinzuzufügen, die enorm verbessert die Leistung des Einfügens von Datensätzen mit LINQ.

(Alle Quellcode vorgesehen sind, bereit zu Ihrer eigenen Anwendung hinzugefügt werden.)

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

Sie müßten nur drei Änderungen an Ihren Code zu machen, und Glied in der Klasse. Viel Glück!