2011-05-01 17 views
6

Historia¿Cómo acelerar las inserciones LINQ con SQL CE?

que tienen una lista de "registros" (3500), que guardo a XML y comprimir al salir del programa. Desde:

  • el número de los registros aumenta
  • sólo alrededor de 50 registros deben ser actualizados en la salida
  • de guardado tiene unos 3 segundos

que necesitaba otra solución - base de datos integrada. Elegí SQL CE, ya que funciona con VS sin ningún problema y la licencia está bien para mí (he comparado a Firebird, SQLite, EffiProz, db4o y BerkeleyDB).

Los datos

La estructura de registro: 11 campos, 2 de ellas se integra en la clave principal (nvarchar + byte). Otros registros son bytes, datatimes, double y ints.

No utilizo ninguna relación, combinación, índice (excepto la clave principal), disparadores, vistas, etc. En realidad, es un diccionario plano: pares de clave + valor. Modifico algunos de ellos, y luego tengo que actualizarlos en la base de datos. De vez en cuando agrego algunos "registros" nuevos y necesito almacenarlos (insertarlos). Eso es todo.

enfoque LINQ

tengo la base de datos en blanco (archivo), así que hago 3500 insertos en un bucle (uno por uno). Ni siquiera compruebo si el registro ya existe porque db está en blanco.

¿Tiempo de ejecución? 4 minutos, 52 segundos. Me desmayé (fíjate: XML + compress = 3 segundos).

enfoque prima

SQL CE Googled un poco, ya pesar de las reclamaciones que aquí: LINQ to SQL (CE) speed versus SqlCe indicando que es en sí SQL CE falta que le di una oportunidad.

El mismo bucle, pero esta vez las inserciones se realizan con SqlCeResultSet (modo DirectTable, consulte: Bulk Insert In SQL Server CE) y SqlCeUpdatableRecord.

¿El resultado? ¿Te sientas cómodamente? Bueno ... 0.3 segundos (¡sí, fracción del segundo!).

El problema

LINQ es muy legible, y las operaciones primas son todo lo contrario. Podría escribir un asignador que traduzca todos los índices de columna a nombres significativos, pero parece que reinventar la rueda, después de todo, ya está hecho en ... LINQ.

¿Entonces tal vez es una forma de decirle a LINQ que acelere las cosas? PREGUNTA - cómo hacerlo?

El código

LINQ

foreach (var entry in dict.Entries.Where(it => it.AlteredByLearning)) 
{ 
    PrimLibrary.Database.Progress record = null; 

     record = new PrimLibrary.Database.Progress(); 
     record.Text = entry.Text; 
     record.Direction = (byte)entry.dir; 
     db.Progress.InsertOnSubmit(record); 

    record.Status = (byte)entry.LastLearningInfo.status.Value; 
    // ... and so on 

    db.SubmitChanges(); 
} 

operaciones primas

SqlCeCommand cmd = conn.CreateCommand();

cmd.CommandText = "Progress"; cmd.CommandType = System.Data.CommandType.TableDirect; SqlCeResultSet rs = cmd.ExecuteResultSet (ResultSetOptions.Updatable);

foreach (var entry in dict.Entries.Where(it => it.AlteredByLearning)) 
{ 
    SqlCeUpdatableRecord record = null; 

    record = rs.CreateRecord(); 

    int col = 0; 
    record.SetString(col++, entry.Text); 
    record.SetByte(col++,(byte)entry.dir); 
    record.SetByte(col++,(byte)entry.LastLearningInfo.status.Value); 
    // ... and so on 

    rs.Insert(record); 
} 
+0

¿Puede mostrar algunos ejemplos de código? –

+0

@Kevin Pullin, claro, agregué las partes esenciales para entender cómo hago las inserciones en ambos casos. – greenoldman

+0

¿Has probado ['BinaryFormatter'] (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter.aspx)? – svick

Respuesta

8

hacer más trabajo por transacción.

cometa, está generalmente operaciones muy costosas para una base de datos relacional típica como la base de datos debe esperar a que los rubores de disco para asegurar que los datos no se pierde (ACID guarantees y todo eso). El disco duro HDD convencional IO sin controladores especiales es muy lento en este tipo de operación: los datos deben vacíos en el disco físico - ¡quizás solo 30-60 confirmaciones pueden ocurrir un segundo con una sincronización IO entre!

Ver las preguntas frecuentes SQLite: INSERT is really slow - I can only do few dozen INSERTs per second. Ignorando el motor de base de datos diferente, este es exactamente el mismo problema.

Normalmente, LINQ2SQL crea una nueva transacción implícita dentro de SubmitChanges. Para evitar esta transacción implícita/commit (commit son operaciones caras) o bien:

  1. Call SubmitChanges menos (por ejemplo, una vez fuera el bucle) o;

  2. instalación de un ámbito de transacción explícita (ver TransactionScope).

Un ejemplo del uso de un contexto de transacción más grande es:

using (var ts = new TransactionScope()) { 
    // LINQ2SQL will automatically enlist in the transaction scope. 
    // SubmitChanges now will NOT create a new transaction/commit each time. 
    DoImportStuffThatRunsWithinASingleTransaction(); 
    // Important: Make sure to COMMIT the transaction. 
    // (The transaction used for SubmitChanges is committed to the DB.) 
    // This is when the disk sync actually has to happen, 
    // but it only happens once, not 3500 times! 
    ts.Complete(); 
} 

Sin embargo, la semántica de un enfoque utilizando una sola transacción o una sola llamada a SubmitChanges son diferentes que la del código de seguridad llamando SubmitChanges 3500 veces y crea 3500 transacciones implícitas diferentes. En particular, el tamaño de las operaciones atómicas (con respecto a la base de datos) es diferente y puede no ser adecuado para todas las tareas.

Para actualizaciones Linq2Sql, cambiando el modelo de concurrencia optimista (desactivación o simplemente utilizando un campo de marca de tiempo, por ejemplo) puede resultar en pequeñas mejoras de rendimiento. La mejora más importante, sin embargo, provendrá de la reducción del número de confirmaciones que se deben realizar.

Happy coding.

+0

gracias por darles vida. No soy mucho para linq2sql. +1 –

+0

A pesar de que estaba justo frente a mis ojos, ignoré esto a propósito, pensando que en el entorno de escritorio esto es insignificante debido a dos factores: el motor SQL CE no se recarga cada vez, no hay una penalización de conexión. Oh chico, yo estaba tan lejos de la verdad, uno se preparó y en lugar de casi 5 minutos obtuve casi 5 segundos (esta es una magnitud más lenta que DirectTable, pero está bien para mí). Gracias por la ducha fría :-). – greenoldman

5

no soy positivo en esto, pero parece que la llamada db.SubmitChanges() debe hacerse fuera del bucle. tal vez eso aceleraría las cosas?

+0

¡Tienes razón en tu forma de pensar! :-) – greenoldman