2010-10-03 17 views
51

Recentemente ho letto di SQLite e ho pensato di fare un tentativo. Quando inserisco una registrazione, va bene. Ma quando inserisco cento ci vogliono cinque secondi e man mano che il conteggio dei record aumenta, aumenta anche il tempo. Cosa potrebbe esserci di sbagliato? Sto usando lo SQLite Wrapper (system.data.SQlite):SQLite Insert molto lento?

dbcon = new SQLiteConnection(connectionString); 
dbcon.Open(); 

//---INSIDE LOOP 

SQLiteCommand sqlComm = new SQLiteCommand(sqlQuery, dbcon); 

nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 

//---END LOOP 

dbcon.close(); 

risposta

68

Wrap BEGIN \ END dichiarazioni intorno agli inserti di massa. Sqlite è ottimizzato per le transazioni.

dbcon = new SQLiteConnection(connectionString); 
dbcon.Open(); 

SQLiteCommand sqlComm; 
sqlComm = new SQLiteCommand("begin", dbcon); 
sqlComm.ExecuteNonQuery(); 
//---INSIDE LOOP 

sqlComm = new SQLiteCommand(sqlQuery, dbcon); 

nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 

//---END LOOP 
sqlComm = new SQLiteCommand("end", dbcon); 
sqlComm.ExecuteNonQuery(); 
dbcon.close(); 
+8

+1 Questo è menzionato nel [FAQ SQLite, # 19] (http://www.sqlite.org/faq.html#q19): quando si esegue questa operazione senza un inizio/fine, SQLite sta creando una transazione per ogni inserto. –

+1

perché hai usato 3 ExecuteNonQuery dove si può fare il lavoro –

+3

3 'ExecuteNonQuery' perché 1 per' BEGIN', 1 (o più) per ogni 'INSERT' e 1 per' END'.A meno che non abbiate aggiunto tutte le istruzioni SQL a una stringa (delimitata da punto e virgola), sono necessarie più chiamate 'ExecuteNonQuery'. – tidwall

28

Provare avvolgendo tutti i tuoi inserti (aka, un inserto di massa) in un unico transaction:

string insertString = "INSERT INTO [TableName] ([ColumnName]) Values (@value)"; 

SQLiteCommand command = new SQLiteCommand(); 
command.Parameters.AddWithValue("@value", value); 
command.CommandText = insertString; 
command.Connection = dbConnection; 
SQLiteTransaction transaction = dbConnection.BeginTransaction(); 
try 
{ 
    //---INSIDE LOOP 
    SQLiteCommand sqlComm = new SQLiteCommand(sqlQuery, dbcon); 
    nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 
    //---END LOOP 

    transaction.Commit(); 
    return true; 
} 
catch (SQLiteException ex) 
{ 
    transaction.Rollback(); 
} 

Per impostazione predefinita, SQLite wraps every inserts in a transaction, che rallenta il processo:

L'INSERIMENTO è molto lento - posso fare solo poche decine di INSERT al secondo

In realtà, SQLite eseguirà facilmente 50.000 o più istruzioni INSERT al secondo su un computer desktop medio. Ma farà solo poche decine di transazioni al secondo.

La velocità di transazione è limitata dalla velocità dell'unità disco perché (per impostazione predefinita) SQLite in realtà attende che i dati siano effettivamente memorizzati sulla superficie del disco prima che la transazione sia completata. In questo modo, se improvvisamente perdi energia o il tuo sistema operativo si arresta, i tuoi dati sono ancora al sicuro. Per maggiori dettagli, leggi su atomic commit in SQLite ..

Per impostazione predefinita, ciascuna istruzione INSERT è la propria transazione. Ma se si circondano più istruzioni INSERT con BEGIN ... COMMIT, tutti gli inserimenti vengono raggruppati in un'unica transazione. Il tempo necessario per eseguire il commit della transazione viene ammortizzato su tutte le istruzioni di inserimento allegate e quindi il tempo per la dichiarazione di inserimento viene notevolmente ridotto.

8

Vedere "Ottimizzazione delle query SQL" nel file della guida di ADO.NET SQLite.NET.chm. Codice da quella pagina:

using (SQLiteTransaction mytransaction = myconnection.BeginTransaction()) 
{ 
    using (SQLiteCommand mycommand = new SQLiteCommand(myconnection)) 
    { 
    SQLiteParameter myparam = new SQLiteParameter(); 
    int n; 

    mycommand.CommandText = "INSERT INTO [MyTable] ([MyId]) VALUES(?)"; 
    mycommand.Parameters.Add(myparam); 

    for (n = 0; n < 100000; n ++) 
    { 
     myparam.Value = n + 1; 
     mycommand.ExecuteNonQuery(); 
    } 
    } 
    mytransaction.Commit(); 
} 
20

ho letto ovunque che la creazione di transazioni è la soluzione per rallentare SQLite scrive, ma può essere lungo e doloroso per riscrivere il codice e avvolgere tutto il vostro SQLite scrive nelle transazioni.

Ho trovato un metodo molto più semplice, sicuro e molto efficiente: abilito un (disabilitato di default) ottimizzazione di SQLite 3.7.0: lo Write-Ahead-Log (WAL). La documentazione dice che funziona in tutti i sistemi unix (cioè Linux e OSX) e Windows.

Come? Basta eseguire i seguenti comandi dopo l'inizializzazione la tua connessione SQLite:

PRAGMA journal_mode = WAL 
PRAGMA synchronous = NORMAL 

mio codice viene eseguito ora ~ 600% più veloce: la mia suite di test ora gira in 38 secondi invece di 4 minuti :)

+1

Grazie! A proposito, probabilmente è possibile utilizzare la modalità sqlite in memoria per i test. – gavv

+0

questa è probabilmente la soluzione migliore se si dispone di più thread per il salvataggio dei dati e non si desidera eseguire molte modifiche al codice per raggruppare tutti gli inserimenti/gli aggiornamenti in una singola chiamata – cahen