2010-09-20 19 views
8

Estoy insertando una gran cantidad de registros usando LinqToSql desde C# a SqlServer 2008 express DB. Parece que la inserción es muy lenta en esto. Lo siguiente es el fragmento de código.Proceso de inserción muy lento utilizando 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(); 
} 

¿Estoy haciendo algo mal? ¿O utilizar Linq para insertar una gran cantidad de registros es una mala elección?

Actualización: Gracias por todas las respuestas. @ p.campbell: Lo siento por la cantidad de registros, fue un error tipográfico, en realidad es alrededor de 100000. Los registros también oscilan hasta 200k también.

De acuerdo con todas las sugerencias moví esta operación en partes (también un cambio de requisito y una decisión de diseño) y recupero datos en trozos pequeños y los inserto en la base de datos cuando se presenta. He puesto este método InsertData() en la operación de subprocesos y ahora usando SmartThreadPool para crear un grupo de 25 subprocesos para hacer la misma operación. En este escenario, estoy insertando a la vez solo 100 registros. Ahora, cuando intenté esto con la consulta de SQL o SQL, no hizo ninguna diferencia en términos de tiempo.

Según mi requerimiento, esta operación está programada para ejecutarse cada hora y recupera registros para alrededor de 4k-6k usuarios. Entonces, ahora estoy agrupando cada operación de datos de usuario (recuperación e inserción en DB) como una tarea y asignada a un hilo. Ahora todo este proceso toma alrededor de 45 minutos para alrededor de 250k registros.

¿Hay alguna forma mejor de realizar este tipo de tareas? ¿O alguien puede sugerirme cómo puedo mejorar este proceso?

+1

¿cuántos registros y cuánto tiempo dura la operación? ¿Qué tipos de datos se están utilizando aquí? –

+0

Más de 1000000 registros y la mayoría de los tipos de datos de cadenas pero no más de 10 campos. – JPReddy

+1

Un millón de inserciones llevará tiempo, pase lo que pase. ¡Sospecho que si copias las declaraciones SQL generadas, las 1 millón de ellas, y las ejecutas ad-hoc, no verás una gran diferencia de rendimiento de Management Studio! –

Respuesta

11

Para insertar cantidad masiva de datos en SQL en un Oner

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

Puede usar el SqlBulkCopy class que proporciona acceso administrado a la utilidad bcp para cargar datos a granel en Sql prácticamente desde cualquier fuente de datos.

La clase SqlBulkCopy se puede utilizar para escribir datos solo en tablas de SQL Server. Sin embargo, la fuente de datos no está limitada a SQL Server; se puede usar cualquier fuente de datos, siempre que los datos se puedan cargar en una instancia de DataTable o leer con una instancia de IDataReader.

Comparación de rendimiento

SqlBulkCopy es, con mucho, el más rápido, incluso cuando la carga de datos de un archivo CSV simple.

Linq generará una carga de sentencias Insert en SQL y las enviará a su servidor SQL. Esto no es diferente de usted usando consultas Ad-hoc con SqlCommand. El rendimiento de SqlCommand vs. Linq es virtualmente idéntico.

La prueba

(SQL Express 2008, .Net 4,0)

SqlBulkCopy

Usando SqlBulkCopy para cargar 100000 filas de un archivo CSV (incluyendo la carga de los datos)

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

Resultados

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

Ese rendimiento es realmente excelente. Gracias por esa sugerencia, estoy usando eso ahora. – JPReddy

3

si está insertando un gran registro de datos que puede probar con BULK INSERT.

Según mi conocimiento, no existe un equivalente de inserción masiva en Linq a SQL.

3

Se llama al SubmitChanges() una vez, lo cual es bueno. Esto significa que solo se usa una conexión y transacción.

Considere refaccionar el código para usar InsertAllOnSubmit() en su lugar.

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

Las instrucciones INSERT se envían una por una como las anteriores, pero quizás esto podría ser más legible?

Algunas otras cosas que preguntar/considerar:

  • ¿Cuál es el estado de los índices de la tabla de destino? Demasiados ralentizarán las escrituras. * ¿La base de datos está en el modelo de recuperación simple o completa?
  • Capture las sentencias de SQL que pasan por el cable. Reproduzca esas declaraciones en una consulta ad hoc contra su base de datos de SQL Server. Me doy cuenta de que está utilizando SQL Express, y probablemente no tenga SQL Profiler. Use context.Log = Console.Out; para output your LINQ To SQL statements to the console. Sin embargo, prefiera el Analizador de SQL por comodidad.
  • ¿Las instrucciones SQL capturadas funcionan de la misma manera que su código de cliente? Si es así, entonces el problema de rendimiento está en el lado de la base de datos.
+0

¿Cómo funciona esto internamente? – cjk

+0

Gracias por la entrada. Refactorización está hecho. No hay mucha mejoría.No hay índices más que el campo id que es la clave principal y generado automáticamente. – JPReddy

+0

@JPReddy: cosas buenas. Estaría interesado en ver el desempeño de las declaraciones SQL adhoc'd capturadas. –

1

Aquí está un agradable paseo a través de la forma de agregar una clase a granel-Insertar para su aplicación, lo que mejora enormemente el rendimiento de insertar registros usando LINQ.

(Se proporciona todo el código fuente, listo para ser añadido a su propia aplicación.)

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

Sólo tendría que hacer tres cambios en su código, y enlace de la clase proporcionada. ¡Buena suerte!