2010-10-03 21 views
51

J'ai récemment lu à propos de SQLite et j'ai pensé que j'essaierais. Quand j'insère un enregistrement, ça fonctionne bien. Mais quand j'en insère cent, cela prend cinq secondes, et au fur et à mesure que le nombre d'enregistrements augmente, le temps passe. Quel pourrait être le problème? J'utilise le SQLite Wrapper (system.data.SQlite):SQLite Insert très lent?

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

//---INSIDE LOOP 

SQLiteCommand sqlComm = new SQLiteCommand(sqlQuery, dbcon); 

nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 

//---END LOOP 

dbcon.close(); 

Répondre

68

Wrap BEGIN \ END déclarations autour de vos inserts en vrac. Sqlite est optimisé pour les transactions.

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 Ceci est mentionné dans le [SQLite FAQ, # 19] (http://www.sqlite.org/faq.html#q19) - lorsque vous faites ceci sans début/fin, SQLite crée une transaction pour chaque insertion. –

+1

pourquoi vous avez utilisé 3 ExecuteNonQuery où l'on peut faire le travail –

+3

3 'ExecuteNonQuery's parce que 1 pour le' BEGIN', 1 (ou plus) pour chaque 'INSERT' et 1 pour' END'.Sauf si vous avez ajouté toutes vos instructions SQL à une chaîne (délimitée par des points-virgules), vous avez besoin de plusieurs appels 'ExecuteNonQuery'. – tidwall

28

Essayez d'envelopper tous vos inserts (aka, un insert en vrac) en un seul 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(); 
} 

Par défaut, SQLite wraps every inserts in a transaction, ce qui ralentit le processus:

INSERT est vraiment lent - je ne peux faire que quelques douzaines INSERTs par seconde

En fait, SQLite va facilement faire 50 000 instructions INSERT ou plus par seconde sur un ordinateur de bureau moyen. Mais il ne fera que quelques dizaines de transactions par seconde.

La vitesse de transaction est limitée par la vitesse de l'unité de disque car (par défaut) SQLite attend jusqu'à ce que les données soient stockées en toute sécurité sur la surface du disque avant la fin de la transaction. De cette façon, si vous perdez soudainement de la puissance ou si votre OS tombe en panne, vos données sont toujours en sécurité. Pour plus de détails, lisez à propos de la validation atomique dans SQLite.

Par défaut, chaque instruction INSERT est sa propre transaction. Mais si vous entourez plusieurs instructions INSERT avec BEGIN ... COMMIT, toutes les insertions sont regroupées en une seule transaction. Le temps nécessaire pour valider la transaction est amorti sur toutes les instructions d'insertion incluses, ce qui réduit considérablement le temps d'insertion.

8

Voir "Optimisation des requêtes SQL" dans le fichier d'aide ADO.NET SQLite.NET.chm. Code de cette page:

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

Je lis partout que la création de transactions est la solution pour ralentir écrit SQLite, mais il peut être long et pénible à réécrire votre code et envelopper tous vos SQLite écrit dans les transactions.

J'ai trouvé une méthode beaucoup plus simple, sûre et très efficace: J'accepte (désactivé par défaut) l'optimisation SQLite 3.7.0: le Write-Ahead-Log (WAL). La documentation indique que cela fonctionne dans tous les systèmes Unix (Linux et OSX) et Windows.

Comment? Il suffit d'exécuter les commandes suivantes après l'initialisation de la connexion SQLite:

PRAGMA journal_mode = WAL 
PRAGMA synchronous = NORMAL 

Mon code fonctionne maintenant ~ 600% plus rapide: ma suite de test fonctionne maintenant en 38 secondes au lieu de 4 minutes :)

+1

Merci! BTW, vous pouvez probablement utiliser le mode sqlite en mémoire pour les tests. – gavv

+0

c'est probablement la meilleure solution si vous avez plusieurs threads en train d'enregistrer des données et que vous ne voulez pas faire beaucoup de changements de code pour regrouper toutes les insertions/mises à jour en un seul appel – cahen