Die Optimierung von SQLite ist schwierig. Die Bulk-Insert-Leistung einer C-Anwendung kann von 85 Inserts pro Sekunde bis zu über 96.000 Inserts pro Sekunde variieren!
Hintergrund: Wir verwenden SQLite als Teil einer Desktop-Anwendung. In XML-Dateien sind große Mengen an Konfigurationsdaten gespeichert, die analysiert und zur weiteren Verarbeitung bei der Initialisierung der Anwendung in eine SQLite-Datenbank geladen werden. SQLite ist ideal für diese Situation, da es schnell ist, keine spezielle Konfiguration erfordert und die Datenbank als einzelne Datei auf der Festplatte gespeichert ist.
Begründung: Anfangs war ich von der Leistung, die ich sah, enttäuscht. Es stellt sich heraus, dass die Leistung von SQLite erheblich variieren kann (sowohl für Masseneinfügungen als auch für Auswahlen), je nachdem, wie die Datenbank konfiguriert ist und wie Sie die API verwenden. Es war keine triviale Angelegenheit, herauszufinden, welche Optionen und Techniken es gab. Daher hielt ich es für ratsam, diesen Community-Wiki-Eintrag zu erstellen, um die Ergebnisse mit Stack Overflow-Lesern zu teilen und anderen die Mühe derselben Untersuchungen zu ersparen.
Das Experiment: Anstatt nur über Leistungstipps im allgemeinen Sinne zu sprechen (dh "Verwenden Sie eine Transaktion!" ), Hielt ich es für das Beste, C-Code zu schreiben und die Auswirkungen verschiedener Optionen tatsächlich zu messen . Wir beginnen mit einigen einfachen Daten:
- Eine 28 MB TAB-getrennte Textdatei (ca. 865.000 Datensätze) des vollständigen Transitplans für die Stadt Toronto
- Mein Testcomputer ist ein 3,60 GHz P4 unter Windows XP.
- Der Code wird mit Visual C ++ 2005 als "Release" mit "Full Optimization" (/ Ox) und Favor Fast Code (/ Ot) kompiliert .
- Ich verwende die SQLite "Amalgamation", die direkt in meine Testanwendung kompiliert wurde. Die SQLite-Version, die ich zufällig habe, ist etwas älter (3.6.7), aber ich vermute, dass diese Ergebnisse mit der neuesten Version vergleichbar sind (bitte hinterlassen Sie einen Kommentar, wenn Sie etwas anderes denken).
Schreiben wir einen Code!
Der Code: Ein einfaches C-Programm, das die Textdatei zeilenweise liest, die Zeichenfolge in Werte aufteilt und die Daten dann in eine SQLite-Datenbank einfügt. In dieser "Basis" -Version des Codes wird die Datenbank erstellt, aber wir werden keine Daten einfügen:
/*************************************************************
Baseline code to experiment with SQLite performance.
Input data is a 28 MB TAB-delimited text file of the
complete Toronto Transit System schedule/route info
from http://www.toronto.ca/open/datasets/ttc-routes/
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"
#define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt"
#define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.sqlite"
#define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)"
#define BUFFER_SIZE 256
int main(int argc, char **argv) {
sqlite3 * db;
sqlite3_stmt * stmt;
char * sErrMsg = 0;
char * tail = 0;
int nRetCode;
int n = 0;
clock_t cStartClock;
FILE * pFile;
char sInputBuf [BUFFER_SIZE] = "\0";
char * sRT = 0; /* Route */
char * sBR = 0; /* Branch */
char * sVR = 0; /* Version */
char * sST = 0; /* Stop Number */
char * sVI = 0; /* Vehicle */
char * sDT = 0; /* Date */
char * sTM = 0; /* Time */
char sSQL [BUFFER_SIZE] = "\0";
/*********************************************/
/* Open the Database and create the Schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
/*********************************************/
/* Open input file and import into Database*/
cStartClock = clock();
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
fgets (sInputBuf, BUFFER_SIZE, pFile);
sRT = strtok (sInputBuf, "\t"); /* Get Route */
sBR = strtok (NULL, "\t"); /* Get Branch */
sVR = strtok (NULL, "\t"); /* Get Version */
sST = strtok (NULL, "\t"); /* Get Stop Number */
sVI = strtok (NULL, "\t"); /* Get Vehicle */
sDT = strtok (NULL, "\t"); /* Get Date */
sTM = strtok (NULL, "\t"); /* Get Time */
/* ACTUAL INSERT WILL GO HERE */
n++;
}
fclose (pFile);
printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);
sqlite3_close(db);
return 0;
}
Die Kontrolle"
Wenn Sie den Code so ausführen, wie er ist, werden keine Datenbankoperationen ausgeführt, aber wir erhalten eine Vorstellung davon, wie schnell die E / A- und Zeichenfolgenverarbeitungsoperationen der C-Rohdatei sind.
864913 Datensätze in 0,94 Sekunden importiert
Großartig! Wir können 920.000 Einfügungen pro Sekunde machen, vorausgesetzt, wir machen eigentlich keine Einfügungen :-)
Das "Worst-Case-Szenario"
Wir werden die SQL-Zeichenfolge mit den aus der Datei gelesenen Werten generieren und diese SQL-Operation mit sqlite3_exec aufrufen:
sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM);
sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg);
Dies wird langsam sein, da SQL für jede Einfügung in VDBE-Code kompiliert wird und jede Einfügung in einer eigenen Transaktion erfolgt. Wie langsam?
864913 Datensätze in 9933,61 Sekunden importiert
Huch! 2 Stunden und 45 Minuten! Das sind nur 85 Einsätze pro Sekunde.
Verwenden einer Transaktion
Standardmäßig wertet SQLite jede INSERT / UPDATE-Anweisung innerhalb einer eindeutigen Transaktion aus. Wenn Sie eine große Anzahl von Einfügungen ausführen, ist es ratsam, Ihren Vorgang in eine Transaktion einzuschließen:
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
...
}
fclose (pFile);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
864913 Datensätze in 38,03 Sekunden importiert
Das ist besser. Durch einfaches Verpacken aller unserer Beilagen in einer einzigen Transaktion wurde unsere Leistung auf 23.000 Beilagen pro Sekunde verbessert .
Verwenden einer vorbereiteten Anweisung
Die Verwendung einer Transaktion war eine enorme Verbesserung, aber das Neukompilieren der SQL-Anweisung für jede Einfügung ist nicht sinnvoll, wenn wir immer wieder dasselbe SQL verwenden. Lassen Sie uns sqlite3_prepare_v2
unsere SQL-Anweisung einmal kompilieren und dann unsere Parameter mit folgender Anweisung an diese Anweisung binden sqlite3_bind_text
:
/* Open input file and import into the database */
cStartClock = clock();
sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
sqlite3_prepare_v2(db, sSQL, BUFFER_SIZE, &stmt, &tail);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
fgets (sInputBuf, BUFFER_SIZE, pFile);
sRT = strtok (sInputBuf, "\t"); /* Get Route */
sBR = strtok (NULL, "\t"); /* Get Branch */
sVR = strtok (NULL, "\t"); /* Get Version */
sST = strtok (NULL, "\t"); /* Get Stop Number */
sVI = strtok (NULL, "\t"); /* Get Vehicle */
sDT = strtok (NULL, "\t"); /* Get Date */
sTM = strtok (NULL, "\t"); /* Get Time */
sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
n++;
}
fclose (pFile);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
864913 Datensätze in 16,27 Sekunden importiert
Nett! Es ist ein bisschen mehr Code (vergessen Sie nicht zu nennen sqlite3_clear_bindings
und sqlite3_reset
), aber wir haben mehr als unsere Leistung verdoppelt 53.000 Einsätze pro Sekunde.
PRAGMA synchron = AUS
Standardmäßig wird SQLite angehalten, nachdem ein Schreibbefehl auf Betriebssystemebene ausgegeben wurde. Dies garantiert, dass die Daten auf die Festplatte geschrieben werden. Durch die Einstellung synchronous = OFF
weisen wir SQLite an, die Daten einfach zum Schreiben an das Betriebssystem zu übergeben und dann fortzufahren. Es besteht die Möglichkeit, dass die Datenbankdatei beschädigt wird, wenn der Computer einen katastrophalen Absturz (oder Stromausfall) erleidet, bevor die Daten auf den Plattenteller geschrieben werden:
/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
864913 Datensätze in 12,41 Sekunden importiert
Die Verbesserungen sind jetzt kleiner, aber wir haben bis zu 69.600 Einfügungen pro Sekunde.
PRAGMA journal_mode = MEMORY
Erwägen Sie, das Rollback-Journal durch Auswerten im Speicher zu speichern PRAGMA journal_mode = MEMORY
. Ihre Transaktion wird schneller sein, aber wenn Sie die Stromversorgung verlieren oder Ihr Programm während einer Transaktion abstürzt, kann Ihre Datenbank mit einer teilweise abgeschlossenen Transaktion in einem beschädigten Zustand belassen werden:
/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);
864913 Datensätze in 13,50 Sekunden importiert
Etwas langsamer als die vorherige Optimierung mit 64.000 Einfügungen pro Sekunde.
PRAGMA synchron = OFF und PRAGMA journal_mode = MEMORY
Kombinieren wir die beiden vorherigen Optimierungen. Es ist etwas riskanter (im Falle eines Absturzes), aber wir importieren nur Daten (keine Bank):
/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);
864913 Datensätze in 12.00 Sekunden importiert
Fantastisch! Wir können 72.000 Einfügungen pro Sekunde ausführen.
Verwenden einer In-Memory-Datenbank
Lassen Sie uns nur zum Spaß auf allen vorherigen Optimierungen aufbauen und den Dateinamen der Datenbank neu definieren, damit wir vollständig im RAM arbeiten:
#define DATABASE ":memory:"
864913 Datensätze in 10,94 Sekunden importiert
Es ist nicht besonders praktisch, unsere Datenbank im RAM zu speichern, aber es ist beeindruckend, dass wir 79.000 Einfügungen pro Sekunde ausführen können .
Refactoring von C-Code
Obwohl dies keine spezielle SQLite-Verbesserung ist, gefallen mir die zusätzlichen char*
Zuweisungsoperationen in der while
Schleife nicht. Lassen Sie uns diesen Code schnell umgestalten, um die Ausgabe strtok()
direkt an zu übergeben sqlite3_bind_text()
, und den Compiler versuchen lassen, die Dinge für uns zu beschleunigen:
pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {
fgets (sInputBuf, BUFFER_SIZE, pFile);
sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */
sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Branch */
sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Version */
sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Stop Number */
sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Vehicle */
sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Date */
sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Time */
sqlite3_step(stmt); /* Execute the SQL Statement */
sqlite3_clear_bindings(stmt); /* Clear bindings */
sqlite3_reset(stmt); /* Reset VDBE */
n++;
}
fclose (pFile);
Hinweis: Wir verwenden wieder eine echte Datenbankdatei. In-Memory-Datenbanken sind schnell, aber nicht unbedingt praktisch
864913 Datensätze in 8,94 Sekunden importiert
Durch eine geringfügige Überarbeitung des in unserer Parameterbindung verwendeten Zeichenfolgenverarbeitungscodes konnten 96.700 Einfügungen pro Sekunde ausgeführt werden. Ich denke, man kann mit Sicherheit sagen, dass dies schnell genug ist . Wenn wir anfangen, andere Variablen (z. B. Seitengröße, Indexerstellung usw.) zu optimieren, wird dies unser Maßstab sein.
Zusammenfassung (bisher)
Ich hoffe du bist noch bei mir! Der Grund, warum wir diesen Weg eingeschlagen haben, ist, dass die Leistung von Masseneinfügungen mit SQLite so stark variiert und es nicht immer offensichtlich ist, welche Änderungen vorgenommen werden müssen, um unseren Betrieb zu beschleunigen. Mit demselben Compiler (und denselben Compileroptionen), derselben SQLite-Version und denselben Daten haben wir unseren Code und unsere Verwendung von SQLite optimiert, um von einem Worst-Case-Szenario mit 85 Einfügungen pro Sekunde auf über 96.000 Einfügungen pro Sekunde zu gelangen!
CREATE INDEX dann INSERT vs. INSERT dann CREATE INDEX
Bevor wir mit der SELECT
Leistungsmessung beginnen, wissen wir, dass wir Indizes erstellen werden. In einer der folgenden Antworten wurde vorgeschlagen, dass es beim Masseneinfügen schneller ist, den Index nach dem Einfügen der Daten zu erstellen (im Gegensatz zum erstmaligen Erstellen des Index und dann zum Einfügen der Daten). Lass es uns versuchen:
Index erstellen und dann Daten einfügen
sqlite3_exec(db, "CREATE INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
...
864913 Datensätze in 18,13 Sekunden importiert
Daten einfügen, dann Index erstellen
...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
864913 Datensätze in 13,66 Sekunden importiert
Wie erwartet sind Bulk-Einfügungen langsamer, wenn eine Spalte indiziert ist. Es macht jedoch einen Unterschied, ob der Index nach dem Einfügen der Daten erstellt wird. Unsere Basislinie ohne Index beträgt 96.000 Einfügungen pro Sekunde. Wenn Sie zuerst den Index erstellen und dann Daten einfügen, erhalten Sie 47.700 Einfügungen pro Sekunde. Wenn Sie zuerst die Daten einfügen und dann den Index erstellen, erhalten Sie 63.300 Einfügungen pro Sekunde.
Ich würde gerne Vorschläge für andere Szenarien machen, um es zu versuchen ... Und werde bald ähnliche Daten für SELECT-Abfragen zusammenstellen.
quelle
sqlite3_clear_bindings(stmt);
? Sie legen die Bindungen jedes Mal fest, wenn dies ausreichen sollte: Vor dem ersten Aufruf von sqlite3_step () oder unmittelbar nach sqlite3_reset () kann die Anwendung eine der Schnittstellen sqlite3_bind () aufrufen, um Werte an die Parameter anzuhängen. Jeder Aufruf von sqlite3_bind () überschreibt vorherige Bindungen für denselben Parameter (siehe: sqlite.org/cintro.html ). In den Dokumenten ist nichts für diese Funktion enthalten, was besagt , dass Sie sie aufrufen müssen.feof()
, um die Beendigung Ihrer Eingangsschleife zu steuern. Verwenden Sie das von zurückgegebene Ergebnisfgets()
. stackoverflow.com/a/15485689/827263Antworten:
Mehrere Tipps:
pragma journal_mode
). Es gibtNORMAL
und dann gibt esOFF
, die die Einfügegeschwindigkeit erheblich erhöhen können, wenn Sie nicht zu besorgt sind, dass die Datenbank möglicherweise beschädigt wird, wenn das Betriebssystem abstürzt. Wenn Ihre Anwendung abstürzt, sollten die Daten in Ordnung sein. Beachten Sie, dass in neueren Versionen dieOFF/MEMORY
Einstellungen für Abstürze auf Anwendungsebene nicht sicher sind.PRAGMA page_size
). Durch größere Seiten können Lese- und Schreibvorgänge etwas schneller ausgeführt werden, da größere Seiten im Speicher gespeichert werden. Beachten Sie, dass mehr Speicher für Ihre Datenbank verwendet wird.CREATE INDEX
nach allen Einfügungen aufrufen . Dies ist erheblich schneller als das Erstellen des Index und das anschließende Einfügen.INTEGER PRIMARY KEY
nach Möglichkeit zu einem Schlüssel zu machen, der die implizite eindeutige Zeilennummernspalte in der Tabelle ersetzt.!feof(file)
!Ich habe auch hier und hier ähnliche Fragen gestellt .
quelle
Versuchen Sie es
SQLITE_STATIC
anstelle vonSQLITE_TRANSIENT
für diese Einsätze.SQLITE_TRANSIENT
bewirkt, dass SQLite die Zeichenfolgendaten kopiert, bevor es zurückkehrt.SQLITE_STATIC
teilt mit, dass die von Ihnen angegebene Speicheradresse gültig ist, bis die Abfrage ausgeführt wurde (was in dieser Schleife immer der Fall ist). Auf diese Weise sparen Sie mehrere Zuweisungs-, Kopier- und Freigabevorgänge pro Schleife. Möglicherweise eine große Verbesserung.quelle
Vermeiden Sie
sqlite3_clear_bindings(stmt)
.Der Code im Test legt die Bindungen jedes Mal fest, durch die ausreichend sein sollte.
Das C-API-Intro aus den SQLite-Dokumenten lautet:
In den Dokumenten gibt es nichts zu
sqlite3_clear_bindings
sagen, dass Sie es zusätzlich zum einfachen Festlegen der Bindungen aufrufen müssen.Weitere Details: Avoid_sqlite3_clear_bindings ()
quelle
Auf Masseneinsätzen
Inspiriert von diesem Beitrag und der Frage zum Stapelüberlauf, die mich hierher geführt hat: Ist es möglich, mehrere Zeilen gleichzeitig in eine SQLite-Datenbank einzufügen? - Ich habe mein erstes Git- Repository gepostet :
https://github.com/rdpoor/CreateOrUpdateDiese Masse lädt ein Array von ActiveRecords in MySQL- , SQLite- oder PostgreSQL- Datenbanken. Es enthält die Option, vorhandene Datensätze zu ignorieren, zu überschreiben oder einen Fehler auszulösen. Meine rudimentären Benchmarks zeigen eine 10-fache Geschwindigkeitsverbesserung im Vergleich zu sequentiellen Schreibvorgängen - YMMV.
Ich verwende es in Produktionscode, wo ich häufig große Datenmengen importieren muss, und bin ziemlich zufrieden damit.
quelle
Massenimporte scheinen am besten zu funktionieren, wenn Sie Ihre INSERT / UPDATE- Anweisungen aufteilen können . Ein Wert von ungefähr 10.000 hat für mich bei einer Tabelle mit nur wenigen Zeilen gut funktioniert, YMMV ...
quelle
Wenn Sie sich nur für das Lesen interessieren, ist es etwas schneller (aber möglicherweise veraltete Daten zu lesen), von mehreren Verbindungen aus mehreren Threads zu lesen (Verbindung pro Thread).
Finden Sie zuerst die Elemente in der Tabelle:
dann Seiten einlesen (LIMIT / OFFSET):
wo und werden pro Thread wie folgt berechnet:
für jeden Thread:
Für unsere kleine (200 MB) Datenbank führte dies zu einer Beschleunigung von 50-75% (3.8.0.2 64-Bit unter Windows 7). Unsere Tabellen sind stark nicht normalisiert (1000-1500 Spalten, ungefähr 100.000 oder mehr Zeilen).
Zu viele oder zu kleine Threads reichen nicht aus. Sie müssen sich selbst bewerten und profilieren.
Auch für uns hat SHAREDCACHE die Leistung verlangsamt, daher habe ich PRIVATECACHE manuell eingefügt (weil es für uns global aktiviert wurde).
quelle
Ich kann keinen Gewinn aus Transaktionen erzielen, bis ich cache_size auf einen höheren Wert erhöht habe, d. H.
PRAGMA cache_size=10000;
quelle
cache_size
die Anzahl der zu zwischenspeichernden Seiten und nicht die gesamte RAM-Größe festgelegt wird. Bei einer Standardseitengröße von 4 KB enthält diese Einstellung bis zu 40 MB Daten pro geöffneter Datei (oder pro Prozess, wenn sie mit gemeinsam genutztem Cache ausgeführt wird ).Nachdem ich dieses Tutorial gelesen hatte, versuchte ich es in mein Programm zu implementieren.
Ich habe 4-5 Dateien, die Adressen enthalten. Jede Datei enthält ca. 30 Millionen Datensätze. Ich verwende dieselbe Konfiguration, die Sie vorschlagen, aber meine Anzahl von INSERTs pro Sekunde ist sehr niedrig (~ 10.000 Datensätze pro Sekunde).
Hier schlägt Ihr Vorschlag fehl. Sie verwenden eine einzelne Transaktion für alle Datensätze und eine einzelne Einfügung ohne Fehler / Fehler. Angenommen, Sie teilen jeden Datensatz in mehrere Einfügungen in verschiedenen Tabellen auf. Was passiert, wenn der Rekord gebrochen ist?
Der Befehl ON CONFLICT wird nicht angewendet. Wenn Sie 10 Elemente in einem Datensatz haben und jedes Element in eine andere Tabelle eingefügt werden muss und Element 5 einen CONSTRAINT-Fehler erhält, müssen auch alle vorherigen 4 Einfügungen ausgeführt werden.
Hier kommt also der Rollback. Das einzige Problem beim Rollback ist, dass Sie alle Ihre Einsätze verlieren und von oben beginnen. Wie können Sie das lösen?
Meine Lösung bestand darin, mehrere Transaktionen zu verwenden. Ich beginne und beende alle 10.000 Datensätze eine Transaktion (Fragen Sie nicht, warum diese Nummer die schnellste war, die ich getestet habe). Ich habe ein Array mit einer Größe von 10.000 erstellt und dort die erfolgreichen Datensätze eingefügt. Wenn der Fehler auftritt, führe ich einen Rollback durch, beginne eine Transaktion, füge die Datensätze aus meinem Array ein, schreibe fest und beginne nach dem fehlerhaften Datensatz eine neue Transaktion.
Diese Lösung hat mir geholfen, die Probleme zu umgehen, die ich beim Umgang mit Dateien habe, die fehlerhafte / doppelte Datensätze enthalten (ich hatte fast 4% fehlerhafte Datensätze).
Der von mir erstellte Algorithmus hat mir geholfen, meinen Prozess um 2 Stunden zu reduzieren. Letzter Ladevorgang der Datei 1 Stunde 30 Minuten, der immer noch langsam ist, aber nicht mit den 4 Stunden verglichen wird, die ursprünglich benötigt wurden. Ich habe es geschafft, die Einsätze von 10.000 / s auf ~ 14.000 / s zu beschleunigen
Wenn jemand andere Ideen hat, wie man es beschleunigen kann, bin ich offen für Vorschläge.
UPDATE :
Zusätzlich zu meiner obigen Antwort sollten Sie berücksichtigen, dass Einfügungen pro Sekunde abhängig von der Festplatte sind, die Sie ebenfalls verwenden. Ich habe es auf 3 verschiedenen PCs mit verschiedenen Festplatten getestet und dabei massive Zeitunterschiede festgestellt. PC1 (1 Std. 30 m), PC2 (6 Std.) PC3 (14 Std.), Also begann ich mich zu fragen, warum das so sein sollte.
Nach zweiwöchiger Recherche und Überprüfung mehrerer Ressourcen: Festplatte, RAM, Cache stellte ich fest, dass einige Einstellungen auf Ihrer Festplatte die E / A-Rate beeinflussen können. Durch Klicken auf Eigenschaften auf dem gewünschten Ausgabelaufwerk werden auf der Registerkarte Allgemein zwei Optionen angezeigt. Opt1: Komprimieren Sie dieses Laufwerk. Opt2: Ermöglichen Sie, dass Dateien dieses Laufwerks indiziert werden.
Durch Deaktivieren dieser beiden Optionen dauert es ungefähr 3 Stunden, bis alle 3 PCs fertig sind (1 Stunde und 20 bis 40 Minuten). Wenn Sie auf langsame Einfügungen stoßen, prüfen Sie, ob Ihre Festplatte mit diesen Optionen konfiguriert ist. Sie sparen viel Zeit und Kopfschmerzen beim Versuch, die Lösung zu finden
quelle
Die Antwort auf Ihre Frage lautet, dass das neuere SQLite 3 die Leistung verbessert hat.
Diese Antwort Warum ist das Einfügen von SQLAlchemy mit SQLite 25-mal langsamer als die direkte Verwendung von SQLite3? von SqlAlchemy Orm Author hat 100.000 Einfügungen in 0,5 Sekunden, und ich habe ähnliche Ergebnisse mit Python-Sqlite und SqlAlchemy gesehen. Was mich glauben lässt, dass sich die Leistung mit SQLite 3 verbessert hat.
quelle
Verwenden Sie ContentProvider zum Einfügen der Massendaten in db. Die folgende Methode wird zum Einfügen von Massendaten in die Datenbank verwendet. Dies sollte die INSERT-pro-Sekunde-Leistung von SQLite verbessern.
Rufen Sie die BulkInsert-Methode auf:
Link: https://www.vogella.com/tutorials/AndroidSQLite/article.html Weitere Informationen finden Sie im Abschnitt Verwenden des ContentProvider-Abschnitts
quelle