tricks Verbesserung der INSERT-per-second-Leistung von SQLite?




sqlite3 tricks (8)

Die Optimierung von SQLite ist schwierig. Die Leistung der Bulk-Inserts 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 Desktopanwendung. Wir haben große Mengen an Konfigurationsdaten in XML-Dateien gespeichert, die analysiert und in eine SQLite-Datenbank zur weiteren Verarbeitung geladen werden, wenn die Anwendung initialisiert wird. SQLite ist ideal für diese Situation, da es schnell ist, keine spezielle Konfiguration erforderlich ist und die Datenbank als einzelne Datei auf der Festplatte gespeichert ist.

Begründung: Anfangs war ich enttäuscht von meiner Leistung. Es stellt sich heraus, dass die Leistung von SQLite erheblich variieren kann (sowohl für Bulk-Inserts als auch für selects), je nachdem, wie die Datenbank konfiguriert ist und wie Sie die API verwenden. Es war keine triviale Angelegenheit, die verschiedenen Optionen und Techniken herauszufinden. Daher hielt ich es für klug, diesen Community-Wiki-Eintrag zu erstellen, um die Ergebnisse mit Stack Overflow-Lesern zu teilen, um anderen die Mühe der gleichen Untersuchungen zu ersparen.

Das Experiment: Anstatt einfach über Performance-Tipps im allgemeinen Sinne zu sprechen (z. B. "Transaktion verwenden!" ), Hielt ich es für am besten, etwas C-Code zu schreiben und die Auswirkungen verschiedener Optionen zu messen . Wir werden mit einigen einfachen Daten beginnen:

  • Eine 28 MB große, durch TAB begrenzte Textdatei (ca. 865.000 Datensätze) des gesamten Transitzeitplans für die Stadt Toronto
  • Mein Testgerät ist ein P4 von 3,60 GHz, auf dem Windows XP läuft.
  • 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 sein werden (bitte hinterlassen Sie einen Kommentar, wenn Sie anders denken).

Lass uns Code schreiben!

Der Code: Ein einfaches C-Programm, das die Textdatei Zeile für Zeile liest, die Zeichenfolge in Werte aufteilt und dann die Daten in eine SQLite-Datenbank einfügt. In dieser "Baseline" -Version des Codes wird die Datenbank erstellt, es werden jedoch keine Daten eingefügt:

/*************************************************************
    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, führt er zwar keine Datenbankoperationen aus, er gibt jedoch Aufschluss darüber, wie schnell die E / A- und String-Verarbeitungsoperationen für reine C-Dateien sind.

Importierte 864913 Datensätze in 0,94 Sekunden

Großartig! Wir können 920.000 Einfügungen pro Sekunde durchführen, vorausgesetzt, wir machen eigentlich keine Einfügungen :-)

Das "Worst-Case-Szenario"

Wir generieren die SQL-Zeichenfolge mit den aus der Datei gelesenen Werten und rufen diese SQL-Operation mit sqlite3_exec auf:

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 die SQL für jede Einfügung in VDBE-Code kompiliert wird und jede Einfügung in einer eigenen Transaktion ausgeführt wird. Wie langsam

Importierte 864913 Datensätze in 9933,61 Sekunden

Yikes! 2 Stunden und 45 Minuten! Das sind nur 85 Einsätze pro Sekunde.

Eine Transaktion verwenden

Standardmäßig wertet SQLite jede INSERT / UPDATE-Anweisung innerhalb einer eindeutigen Transaktion aus. Wenn Sie eine große Anzahl von Einfügungen durchführen, ist es ratsam, Ihren Vorgang in eine Transaktion einzuwickeln:

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);

Importierte 864913 Datensätze in 38,03 Sekunden

Das ist besser. Durch das einfache Einpacken aller Einsätze in einer einzigen Transaktion wurde unsere Leistung auf 23.000 Einsätze pro Sekunde verbessert .

Verwenden einer vorbereiteten Anweisung

Die Verwendung einer Transaktion war eine enorme Verbesserung, aber eine erneute Kompilierung der SQL-Anweisung für jede Einfügung ist nicht sinnvoll, wenn wir die gleiche SQL-Funktion verwenden. Verwenden Sie sqlite3_prepare_v2 , um unsere SQL-Anweisung einmal zu kompilieren, und binden Sie dann unsere Parameter mithilfe von sqlite3_bind_text an diese Anweisung:

/* 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;

Importierte 864913 Datensätze in 16,27 Sekunden

Nett! Es gibt etwas mehr Code (vergessen Sie nicht, sqlite3_clear_bindings und sqlite3_reset zu nennen), aber wir haben unsere Leistung auf 53.000 Einfügungen pro Sekunde mehr als verdoppelt .

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 von synchronous = OFF weisen wir SQLite an, die Daten einfach an das Betriebssystem zum Schreiben 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);

Importierte 864913 Datensätze in 12,41 Sekunden

Die Verbesserungen sind jetzt kleiner, aber wir haben bis zu 69.600 Einsätze pro Sekunde.

PRAGMA journal_mode = MEMORY

Speichern Sie das Rollback-Journal, indem Sie PRAGMA journal_mode = MEMORY . Ihre Transaktion wird schneller ausgeführt. Wenn Sie jedoch an Leistung verlieren oder Ihr Programm während einer Transaktion abstürzt, kann Ihre Datenbank mit einer teilweise abgeschlossenen Transaktion in einem beschädigten Zustand verbleiben:

/* 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);

Importierte 864913 Datensätze in 13.50 Sekunden

Etwas langsamer als die vorherige Optimierung mit 64.000 Einsätzen 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 (ohne eine Bank zu betreiben):

/* 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);

Importierte 864913 Datensätze in 12.00 Sekunden

Fantastisch! Wir können 72.000 Einsätze pro Sekunde durchführen.

Verwenden einer In-Memory-Datenbank

Lassen Sie uns auf den bisherigen Optimierungen aufbauen und den Dateinamen der Datenbank neu definieren, damit wir vollständig im RAM arbeiten:

#define DATABASE ":memory:"

Importierte 864913 Datensätze in 10,94 Sekunden

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 .

Umgestaltung des C-Codes

Obwohl es sich nicht speziell um eine Verbesserung von SQLite handelt, mag ich die zusätzlichen char* -Zuweisungen in der while Schleife nicht. Lassen Sie uns diesen Code schnell umgestalten, um die Ausgabe von strtok() direkt in sqlite3_bind_text() , und lassen Sie den Compiler versuchen, 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

Importierte 864913 Datensätze in 8,94 Sekunden

Durch ein leichtes Refactoring des in unserer Parameterbindung verwendeten Zeichenfolgenverarbeitungscodes konnten wir 96.700 Einfügungen pro Sekunde durchführen. Ich denke, es ist mit Sicherheit viel zu sagen. Wenn wir anfangen, andere Variablen (z. B. Seitengröße, Indexerstellung usw.) anzupassen, 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 der Masseneinfügung mit SQLite so stark variiert, und es ist nicht immer offensichtlich, welche Änderungen erforderlich sind, um unseren Betrieb zu beschleunigen. Mit demselben Compiler (und denselben Compileroptionen), derselben Version von SQLite und denselben Daten haben wir unseren Code und unsere Verwendung von SQLite optimiert, um von einem Worst-Case-Szenario von 85 Einfügungen pro Sekunde auf über 96.000 Einfügungen pro Sekunde zu gehen!

CREATE INDEX, dann INSERT vs. INSERT, dann CREATE INDEX

Bevor wir mit der Messung der SELECT Leistung beginnen, wissen wir, dass wir Indizes erstellen werden. In einer der folgenden Antworten wurde angedeutet, dass es beim Erstellen von Masseneinfügungen schneller ist, den Index zu erstellen, nachdem die Daten eingefügt wurden (anstatt den Index zuerst zu erstellen und dann die Daten einzufügen). Lass es uns versuchen:

Index erstellen, 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);
...

Importierte 864913 Datensätze in 18,13 Sekunden

Daten einfügen und Index erstellen

...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);

Importierte 864913 Datensätze in 13,66 Sekunden

Wie erwartet, sind Bulk-Einfügungen langsamer, wenn eine Spalte indiziert wird. Es macht jedoch einen Unterschied, wenn der Index nach dem Einfügen der Daten erstellt wird. Unser Basiswert ohne Index beträgt 96.000 Einsätze pro Sekunde. Wenn Sie zuerst den Index erstellen, dann Daten einfügen, erhalten Sie 47.700 Einfügungen pro Sekunde, wohingegen das Einfügen der Daten zuerst und das Erstellen des Indexes 63.300 Einfügungen pro Sekunde ergeben.

Ich würde gerne Vorschläge für andere Szenarien nehmen, um es auszuprobieren ... Und ähnliche Daten für SELECT-Abfragen werden in Kürze zusammengestellt.


Nachdem ich dieses Tutorial gelesen hatte, versuchte ich es in meinem Programm zu implementieren.

Ich habe 4-5 Dateien, die Adressen enthalten. Jede Datei hat 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 versagt Ihr Vorschlag. Sie verwenden eine einzige Transaktion für alle Datensätze und eine einzelne Einfügung ohne Fehler / Fehler. Angenommen, Sie teilen jeden Datensatz in mehrere Einfügungen an verschiedenen Tabellen auf. Was passiert, wenn der Datensatz beschädigt 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. Wenn Element 5 einen CONSTRAINT-Fehler erhält, müssen alle vorherigen 4 Einfügungen ebenfalls ausgeführt werden.

Hier kommt also der Rollback. Das einzige Problem mit dem Rollback ist, dass Sie alle Einfügungen verlieren und von oben beginnen. Wie kannst du das lösen?

Meine Lösung bestand darin, mehrere Transaktionen zu verwenden. Ich beginne und beende eine Transaktion alle 10.000 Datensätze (Fragen Sie nicht nach der Nummer, es war die schnellste, die ich getestet habe). Ich habe ein Array mit der Größe 10.000 erstellt und die erfolgreichen Datensätze dort eingefügt. Wenn der Fehler auftritt, führe ich einen Rollback durch, fange eine Transaktion an, füge die Datensätze aus meinem Array ein, bestätige und fange nach dem fehlerhaften Datensatz eine neue Transaktion an.

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 entwickelte Algorithmus half mir, meinen Prozess um 2 Stunden zu reduzieren. Endgültiger Ladevorgang der Datei 1 Stunde 30 Minuten, der immer noch langsam ist, jedoch nicht mit den 4 Stunden verglichen, die er anfangs benötigt hat. Es gelang mir, die Einsätze von 10.000 / s auf ~ 14.000 / s zu beschleunigen

Wenn jemand andere Ideen hat, wie er beschleunigt werden kann, bin ich offen für Vorschläge.

UPDATE :

Zusätzlich zu meiner obigen Antwort sollten Sie berücksichtigen, dass die Einfügungen pro Sekunde auch von der verwendeten Festplatte abhängen. Ich habe es auf drei verschiedenen PCs mit unterschiedlichen Festplatten getestet und dabei große Zeitunterschiede festgestellt. PC1 (1 Stunde 30 Minuten), PC2 (6 Stunden) PC3 (14 Stunden), also habe ich mich gefragt, warum das so sein könnte.

Nach zwei Wochen Recherche und Überprüfung mehrerer Ressourcen: Festplatte, RAM, Cache, habe ich festgestellt, dass einige Einstellungen auf Ihrer Festplatte die E / A-Rate beeinflussen können. Durch Klicken auf Eigenschaften auf dem gewünschten Ausgabe-Laufwerk können Sie zwei Optionen auf der Registerkarte Allgemein sehen. Opt1: Komprimieren Sie dieses Laufwerk, Opt2: Lassen Sie Dateien dieses Laufwerks indizieren.

Durch Deaktivieren dieser beiden Optionen benötigen alle 3 PCs nun ungefähr die gleiche Zeit (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. Es wird Ihnen viel Zeit und Kopfschmerzen ersparen, die Lösung zu finden


Massenimporte scheinen am besten zu funktionieren, wenn Sie Ihre INSERT / UPDATE- Anweisungen zusammenfassen können. Ein Wert von 10.000 oder so hat bei einer Tabelle mit nur wenigen Zeilen, YMMV, gut funktioniert.


Auf Großteileinsätzen

Inspiriert von diesem Beitrag und von der -Frage, 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/CreateOrUpdate

welcher Bulk lädt ein Array von ActiveRecords in MySQL , SQLite- oder PostgreSQL Datenbanken. Es enthält eine Option, um vorhandene Datensätze zu ignorieren, sie zu überschreiben oder einen Fehler auszulösen. Meine rudimentären Benchmarks zeigen eine 10fache Geschwindigkeitsverbesserung im Vergleich zu sequentiellen Schreibvorgängen - YMMV.

Ich verwende es im Produktionscode, wo ich häufig große Datensätze importieren muss, und bin damit ziemlich zufrieden.


Wenn Sie sich nur für das Lesen interessieren, lesen Sie etwas schneller (aber möglicherweise veraltete Daten lesen) die Version aus mehreren Verbindungen aus mehreren Threads (Verbindung pro Thread).

Finden Sie zuerst die Artikel in der Tabelle:

 SELECT COUNT(*) FROM table

dann in Seiten einlesen (LIMIT / OFFSET)

  SELECT * FROM table ORDER BY _ROWID_ LIMIT <limit> OFFSET <offset>

wo und werden pro Thread wie folgt berechnet:

int limit = (count + n_threads - 1)/n_threads;

für jeden Thread:

int offset = thread_index * limit

Für unsere kleine (200 MB) db beschleunigte das 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 wenige Threads tun dies nicht, Sie müssen sich selbst Benchmark und Profil erstellen.

Auch für uns hat SHAREDCACHE die Leistung langsamer gemacht, deshalb habe ich PRIVATECACHE manuell eingestellt (weil es global für uns aktiviert war).


Einige Tipps:

  1. Fügen Sie Einfügungen / Aktualisierungen in eine Transaktion ein.
  2. Für ältere Versionen von SQLite - Betrachten Sie einen weniger paranoiden pragma journal_mode ( pragma journal_mode ). Es gibt NORMAL und dann OFF NORMAL kann die Einfügungsgeschwindigkeit erheblich erhöhen, wenn Sie sich nicht zu NORMAL Sorgen machen, 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 die Einstellungen für OFF/MEMORY nicht für Abstürze auf Anwendungsebene sicher sind.
  3. Das Spielen mit Seitengrößen macht ebenfalls einen Unterschied ( PRAGMA page_size ). Größere Seitengrößen können zu Lese- und Schreibvorgängen führen, da größere Seiten im Speicher abgelegt werden. Beachten Sie, dass mehr Speicher für Ihre Datenbank verwendet wird.
  4. Wenn Sie über Indizes verfügen, sollten Sie CREATE INDEX aufrufen, nachdem Sie alle Einfügungen vorgenommen haben. Dies ist wesentlich schneller als das Erstellen des Index und das Durchführen der Einfügungen.
  5. Sie müssen sehr vorsichtig sein, wenn Sie gleichzeitig auf SQLite zugreifen können, da die gesamte Datenbank gesperrt ist, wenn Schreibvorgänge ausgeführt werden. Obwohl mehrere Leser möglich sind, werden Schreibvorgänge gesperrt. Dies wurde durch das Hinzufügen einer WAL in neueren SQLite-Versionen etwas verbessert.
  6. Speicherplatz sparen ... kleinere Datenbanken gehen schneller. Wenn Sie beispielsweise Schlüsselwertpaare haben, versuchen Sie, den Schlüssel zu einem INTEGER PRIMARY KEY Schlüssel zu machen, der die implizierte eindeutige Zeilennummernspalte in der Tabelle ersetzt.
  7. Wenn Sie mehrere Threads verwenden, können Sie versuchen, den Cache für gemeinsam genutzte Seiten zu verwenden. Dadurch können geladene Seiten von Threads gemeinsam verwendet werden, wodurch kostspielige E / A-Aufrufe vermieden werden.
  8. Verwenden Sie nicht !feof(file) !

Ich habe auch here und here ähnliche Fragen gestellt.



Ich konnte keinen Gewinn aus Transaktionen ziehen, bis ich cache_size auf einen höheren Wert erhöhte, dh PRAGMA cache_size=10000;


Verwenden Sie SQLITE_STATIC anstelle von SQLITE_TRANSIENT für diese Einfügungen.

SQLITE_TRANSIENT , dass SQLite die String-Daten vor der Rückgabe kopiert.

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). Dadurch sparen Sie mehrere Operationen zum Zuweisen, Kopieren und Freigeben von Operationen pro Schleife. Möglicherweise eine große Verbesserung.





optimization