2010-12-04 10 views
12

En mi sistema, ~ 86000 inserciones SQLite tomaron hasta 20 minutos, significa ~ 70 inserciones por segundo. Tengo que hacer millones, ¿cómo puedo acelerarlo? ¿Llamar a Open() y Close() en el objeto SQLiteConnection para cada línea puede ralentizar el rendimiento? ¿Pueden las transacciones ayudar?SQLite .NET rendimiento, ¿cómo acelerar las cosas?

método de inserción típica para una sola línea:

public int InsertResultItem(string runTag, int topicId, 
     string documentNumber, int rank, double score) 
    { 
     // Apre la connessione e imposta il comando 
     connection.Open(); 

     command.CommandText = "INSERT OR IGNORE INTO Result " 
      + "(RunTag, TopicId, DocumentNumber, Rank, Score) " + 
      "VALUES (@RunTag, @TopicId, @DocumentNumber, @Rank, @Score)"; 

     // Imposta i parametri 
     command.Parameters.AddWithValue("@RunTag", runTag); 
     command.Parameters.AddWithValue("@TopicId", topicId); 
     command.Parameters.AddWithValue("@DocumentNumber", documentNumber); 
     command.Parameters.AddWithValue("@Rank", rank); 
     command.Parameters.AddWithValue("@Score", score); 

     // Ottieni il risultato e chiudi la connessione 
     int retval = command.ExecuteNonQuery(); 
     connection.Close(); 

     return retval; 
    } 

Como se puede ver, las inserciones son muy simples.

Respuesta

23

Definitivamente necesita una transacción. Si no lo hace, SQLite inicia su propia transacción para cada comando de inserción, por lo que efectivamente está haciendo 86000 transacciones como está.

Parece que también está abriendo y cerrando la conexión cada vez, junto con el restablecimiento del CommandText cada vez. Esto es innecesario y sin duda afectar el rendimiento, que va a ir mucho más rápido si:

  • Abra la conexión una vez
  • construir el comando una vez, añadiendo los parámetros de una vez.
  • iniciar la transacción
  • bucle a través, cambiando los valores de los parámetros solamente antes de llamar ExecuteNonQuery
  • confirmar la transacción.
  • Cierre la conexión.

Creo que podría reducir sus 20 minutos a unos pocos segundos de esta manera.

Editar: esto es lo que quiero decir:

public void InsertItems() 
{ 
    SQLiteConnection connection = new SQLiteConnection(SomeConnectionString); 
    SQLiteCommand command = connection.CreateCommand(); 
    SQLiteTransaction transaction = connection.BeginTransaction(); 

    command.CommandText = "INSERT OR IGNORE INTO Result " 
+ "(RunTag, TopicId, DocumentNumber, Rank, Score) " + 
    "VALUES (@RunTag, @TopicId, @DocumentNumber, @Rank, @Score)"; 

    command.Parameters.AddWithValue("@RunTag", ""); 
    command.Parameters.AddWithValue("@TopicId", ""); 
    command.Parameters.AddWithValue("@DocumentNumber", ""); 
    command.Parameters.AddWithValue("@Rank", ""); 
    command.Parameters.AddWithValue("@Score", ""); 

    foreach (/* item to loop through and add to db */) 
    { 
     InsertResultItem(runTag, topicId, documentNumber, rank, score, command); 
    } 

    transaction.Commit(); 
    command.Dispose(); 
    connection.Dispose(); 
} 

public int InsertResultItem(string runTag, int topicId, string documentNumber, int rank, double score, SQLiteCommand command) 
{ 
    command.Parameters["@RunTag"].Value = runTag; 
    command.Parameters["@TopicId"].Value = topicId; 
    command.Parameters["@DocumentNumber"].Value = documentNumber; 
    command.Parameters["@Rank"].Value = rank; 
    command.Parameters["@Score"].Value = score; 
    return command.ExecuteNonQuery(); 
} 

Sólo se utiliza una conexión, una transacción y un comando, por lo que todo lo que está cambiando es los valores de los parámetros cada vez.

+0

No entiendo el punto n. ° 2, ¿me lo puede explicar? – gremo

+0

@Gremo, lo he editado para mostrar un ejemplo. – mikel

+0

No puedo, simplemente porque el ciclo está en otra clase ... ¿qué hay de implementar IDisposable y comprometer la transacción en Dispose()? – gremo

3

Usar transacciones. Esto debería hacer las cosas más rápido. También le recomendaría el siguiente patrón:

public int InsertResultItem(string runTag, int topicId, 
    string documentNumber, int rank, double score) 
{ 
    // Apre la connessione e imposta il comando 
    using (var connection = new SQLiteConnection(SomeConnectionString)) 
    using (var command = new connection.CreateCommand()) 
    { 
     connection.Open(); 
     using (var tx = connection.BeginTransaction()) 
     { 
      command.CommandText = "INSERT OR IGNORE INTO Result " 
       + "(RunTag, TopicId, DocumentNumber, Rank, Score) " + 
       "VALUES (@RunTag, @TopicId, @DocumentNumber, @Rank, @Score)"; 

      // Imposta i parametri 
      command.Parameters.AddWithValue("@RunTag", runTag); 
      command.Parameters.AddWithValue("@TopicId", topicId); 
      command.Parameters.AddWithValue("@DocumentNumber", documentNumber); 
      command.Parameters.AddWithValue("@Rank", rank); 
      command.Parameters.AddWithValue("@Score", score); 

      // Ottieni il risultato e chiudi la connessione 
      var retval = command.ExecuteNonQuery(); 
      tx.Commit(); 
      return retval; 
     } 
    } 
} 
+0

Dado que InsertResultItem parece que se llamaría en un bucle, todavía se está abriendo una nueva conexión, comenzando una nueva transacción y haciendo un nuevo comando para cada inserción. La velocidad sería la misma que en el código del OP. – mikel

+0

@ miket2e, esto no está abriendo una nueva conexión, ADO.NET usa un grupo de conexiones. También comenzar una nueva transacción es exactamente lo que acelera las cosas. –

+0

Sin embargo, está creando una transacción para cada inserción. SQLite lo hace implícitamente de todos modos (http://www.sqlite.org/lang_transaction.html) si no lo mencionas explícitamente, eso es lo que quiero decir con que es lo mismo. Tiene que ser una transacción envuelta en todos los encartes para marcar la diferencia. No estoy seguro de si está usando ADO.NET o no, si usa SQLite incrustado en una aplicación (sin ADO.NET) sería más rápido conectarse solo una vez también. – mikel

Cuestiones relacionadas