2010-09-20 9 views
8

Inserisco un numero elevato di record utilizzando LinqToSql da C# a SqlServer 2008 Express DB. Sembra che l'inserimento sia molto lento in questo. Di seguito è riportato lo snippet di codice.Processo di inserimento molto lento tramite Linq a 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(); 
} 

Sto facendo qualcosa di sbagliato? O usare Linq per inserire un numero elevato di record è una cattiva scelta?

Aggiornamento: Grazie per tutte le risposte. @ p.campbell: Ci scusiamo per il numero di record, è stato un errore di battitura, in realtà è di circa 100.000. Anche i record vanno fino a 200k.

Come tutti i suggerimenti ho spostato questa operazione in parti (anche una modifica del requisito e la decisione di progettazione) e il recupero dei dati in piccoli blocchi e inserendoli nel database come e quando viene fornito. Ho messo questo metodo InsertData() nell'operazione thread e ora usando SmartThreadPool per creare un pool di 25 thread per fare la stessa operazione. In questo scenario sto inserendo alla volta solo 100 record. Ora, quando ho provato questo con query Linq o sql, non ha fatto alcuna differenza in termini di tempo impiegato.

Secondo le mie esigenze, questa operazione è pianificata per essere eseguita ogni ora e recupera i record per circa 4k-6k utenti. Quindi, ora sto riunendo tutti i dati utente (recuperando e inserendo in DB) come un'operazione e assegnata a un thread. Ora l'intero processo richiede circa 45 minuti per circa 250k di record.

Esiste un modo migliore per eseguire questo tipo di attività? O qualcuno può suggerirmi come posso migliorare questo processo?

+1

quanti dischi e quanto dura l'operazione? Quali tipi di dati vengono utilizzati qui? –

+0

Oltre 1000000 record e principalmente tipi di dati stringa, ma non più di 10 campi. – JPReddy

+1

Un milione di inserti richiederà tempo, non importa quale. Ho il sospetto che se copi le istruzioni SQL generate, tutte 1 milione di esse, e le esegui ad-hoc, non vedrai molto di una differenza perfetta da Management Studio! –

risposta

11

Per inserire grandi quantità di dati in SQL in un oner

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

È possibile utilizzare SqlBulkCopy class che fornisce accesso gestito all'utilità bcp per il caricamento di massa dei dati in Sql da praticamente qualsiasi origine dati.

La classe SqlBulkCopy può essere utilizzata per scrivere dati solo nelle tabelle di SQL Server. Tuttavia, l'origine dati non è limitata a SQL Server; qualsiasi origine dati può essere utilizzata, a condizione che i dati possano essere caricati su un'istanza DataTable o letti con un'istanza IDataReader.

confronto delle prestazioni

SqlBulkCopy è di gran lunga il più veloce, anche durante il caricamento dei dati da un semplice file CSV.

LINQ sarà solo genera un carico di Insert dichiarazioni in SQL e li invia al server SQL. Non è diverso da quello che usi le query ad-hoc con SqlCommand. Le prestazioni di SqlCommand vs. Linq sono praticamente identiche.

La Prova

(SQL Express 2008, .Net 4,0)

SqlBulkCopy

Utilizzando SqlBulkCopy per caricare 100000 righe da un file CSV (tra cui il caricamento dei dati)

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

Risultati

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

Questa prestazione è davvero eccezionale. Grazie per questo suggerimento, lo sto usando ora. – JPReddy

3

se si sta inserendo un ampio record di dati, è possibile provare con BULK INSERT.

Per quanto ne so non esiste un equivalente di inserimento bulk in Linq to SQL.

3

Hai chiamato il SubmitChanges(), che è buono. Ciò significa che vengono utilizzate solo una connessione e una transazione.

Prendere in considerazione il codice di refactoring per utilizzare InsertAllOnSubmit() invece.

List<dbItem> newItems = GetItems(id).Select(x=> new DbItem{ItemNo = x.No, 
                  ItemName=x.Name}) 
            .ToList(); 
db.InsertAllOnSubmit(newItems); 
dc.SubmitChanges(); 

Le istruzioni INSERT vengono inviate una alla volta come in precedenza, ma forse questo potrebbe essere più leggibile?

Alcune altre cose da chiedere/prendere in considerazione:

  • Qual è lo stato degli indici sulla tabella di destinazione? Troppi rallenteranno le scritture. * Il database è in modello di recupero semplice o completo?
  • Cattura le istruzioni SQL sul filo. Riproduci queste istruzioni in una query ad hoc sul database di SQL Server. Mi rendo conto che stai usando SQL Express e probabilmente non hai SQL Profiler. Utilizzare context.Log = Console.Out; a output your LINQ To SQL statements to the console. Preferisco SQL Profiler per comodità però.
  • Le istruzioni SQL acquisite hanno lo stesso valore del codice client? Se è così, allora il problema di perf è sul lato del database.
+0

Come funziona internamente? – cjk

+0

Grazie per l'input. Il refactoring è fatto. Non molto miglioramento.Nessun indice diverso dal campo ID, che è la chiave primaria e generata automaticamente. – JPReddy

+0

@JPReddy: roba buona. Sarebbe interessato a vedere il perf delle istruzioni SQL acquisite + adhoc'd. –

1

Ecco una bella passeggiata-through di come aggiungere una classe Bulk-Inserisci per la vostra applicazione, che migliora enormemente la performance di inserire record usando LINQ.

(Tutto il codice sorgente è fornito, pronto per essere aggiunto alla propria applicazione.)

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

Si sarebbe solo bisogno di fare tre modifiche al codice, e link nella classe prevista. Buona fortuna!