3383 Stimmen

Verbesserung der INSERT-per-Sekunde-Leistung von SQLite

Die Optimierung von SQLite ist knifflig. Die Bulk-Insert-Leistung einer C-Anwendung kann von 85 Inserts pro Sekunde bis zu über 96.000 Inserts pro Sekunde reichen!

Hintergrund: Wir verwenden SQLite als Teil einer Desktop-Anwendung. Wir haben große Mengen an Konfigurationsdaten in XML-Dateien gespeichert, die geparst 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 erfordert und die Datenbank als einzelne Datei auf der Festplatte gespeichert wird.

Begründung: Anfänglich war ich von der Leistung, die ich sah, enttäuscht. Es hat sich herausgestellt, dass die Leistung von SQLite erheblich variieren kann (sowohl bei Bulk-Inserts als auch bei Selects), je nachdem, wie die Datenbank konfiguriert ist und wie Sie die API verwenden. Es war nicht trivial herauszufinden, welche Optionen und Techniken es gibt. Daher hielt ich es für klug, diesen Wiki-Eintrag zu erstellen, um die Ergebnisse mit den Stack Overflow-Lesern zu teilen und anderen die gleichen Untersuchungen zu ersparen.

Das Experiment: Anstatt einfach über Leistungstipps im allgemeinen Sinne zu sprechen (d.h. "Verwenden Sie eine Transaktion!" ), hielt ich es für das Beste, etwas C-Code zu schreiben und tatsächlich messen die Auswirkungen der verschiedenen Optionen. Wir werden mit einigen einfachen Daten beginnen:

  • Eine 28 MB große TAB-getrennte Textdatei (etwa 865.000 Datensätze) der vollständiger Fahrplan für die Stadt Toronto
  • Mein Testrechner ist ein 3,60 GHz P4 mit Windows XP.
  • Der Code wird kompiliert mit Visual C++ 2005 als "Release" mit "Full Optimization" (/Ox) und Favor Fast Code (/Ot).
  • Ich verwende SQLite "Amalgamation", das 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 anderer Meinung sind).

Schreiben wir etwas Code!

Der Kodex: Ein einfaches C-Programm, das die Textdatei Zeile für Zeile liest, die Zeichenkette in Werte zerlegt und die Daten dann in eine SQLite-Datenbank einfügt. In dieser "Basisversion" 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 zwar keine Datenbankoperationen durchgeführt, aber Sie erhalten einen Eindruck davon, wie schnell die rohen C-Dateieingabe- und Zeichenkettenverarbeitungsoperationen sind.

Importierte 864913 Datensätze in 0,94 Sekunden

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


Das "Worst-Case-Szenario"

Wir werden den SQL-String 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 das SQL für jede Einfügung in VDBE-Code kompiliert wird und jede Einfügung in einer eigenen Transaktion erfolgt. Wie langsam?

Importierte 864913 Datensätze in 9933,61 Sekunden

Igitt! 2 Stunden und 45 Minuten! Das ist nur 85 Einsätze pro Sekunde.

Verwendung einer Transaktion

SQLite wertet standardmäßig jede INSERT / UPDATE-Anweisung innerhalb einer einzigen Transaktion aus. Wenn Sie eine große Anzahl von Einfügungen vornehmen, ist es ratsam, Ihre Operation in eine Transaktion zu verpacken:

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. Wenn wir alle unsere Einfügungen in eine einzige Transaktion packen, verbessert sich unsere Leistung auf 23.000 Einsätze pro Sekunde.

Verwendung einer vorbereiteten Erklärung

Die Verwendung einer Transaktion war eine enorme Verbesserung, aber es macht keinen Sinn, die SQL-Anweisung für jede Einfügung neu zu kompilieren, wenn wir immer wieder die gleiche SQL verwenden. Verwenden wir sqlite3_prepare_v2 um unsere SQL-Anweisung einmal zu kompilieren und dann unsere Parameter an diese Anweisung zu binden, indem wir 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;

Importierte 864913 Datensätze in 16,27 Sekunden

Sehr schön! Es gibt noch ein bisschen mehr Code (vergessen Sie nicht, die sqlite3_clear_bindings y sqlite3_reset ), aber wir haben unsere Leistung mehr als verdoppelt auf 53.000 Einsätze pro Sekunde.

PRAGMA synchron = AUS

Standardmäßig pausiert SQLite nach der Ausgabe eines Schreibbefehls auf Betriebssystemebene. Dadurch wird gewährleistet, dass die Daten auf die Festplatte geschrieben werden. Durch die Einstellung synchronous = OFF wird SQLite angewiesen, 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 die Festplatte 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 sind auf dem Weg 69.600 Einsätze pro Sekunde.

PRAGMA journal_mode = MEMORY

Erwägen Sie die Speicherung des Rollback-Journals im Speicher durch Auswertung von PRAGMA journal_mode = MEMORY . Ihre Transaktion ist zwar schneller, aber wenn Sie während einer Transaktion den Strom verlieren oder Ihr Programm abstürzt, könnte Ihre Datenbank in einem beschädigten Zustand mit einer teilweise abgeschlossenen Transaktion zurückbleiben:

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

Ein wenig langsamer als die vorherige Optimierung bei 64.000 Einsätze pro Sekunde.

PRAGMA synchron = AUS und PRAGMA journal_mode = MEMORY

Kombinieren wir die beiden vorherigen Optimierungen. Das ist etwas riskanter (im Falle eines Absturzes), aber wir importieren nur Daten (und führen 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);

Importierte 864913 Datensätze in 12,00 Sekunden

Fantastisch! Wir sind in der Lage zu tun 72.000 Einsätze pro Sekunde.

Verwendung einer In-Memory-Datenbank

Nur so zum Spaß bauen wir auf allen vorherigen Optimierungen auf und definieren den Dateinamen der Datenbank neu, so dass wir komplett 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 die 79.000 Einsätze pro Sekunde.

Refactoring von C-Code

Obwohl es sich nicht speziell um eine SQLite-Verbesserung handelt, mag ich die zusätzliche char* Zuordnungsvorgänge in der while Schleife. Lassen Sie uns diesen Code schnell umstrukturieren, 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 jetzt wieder eine echte Datenbankdatei. In-Memory-Datenbanken sind schnell, aber nicht unbedingt praktisch

Importierte 864913 Datensätze in 8,94 Sekunden

Durch eine geringfügige Umstrukturierung des Codes für die Verarbeitung von Zeichenketten, der in unserer Parameterbindung verwendet wird, können wir nun 96.700 Einsätze pro Sekunde. Ich denke, man kann mit Sicherheit sagen, dass dies eine sehr schnell . Wenn wir beginnen, andere Variablen zu optimieren (z. B. Seitengröße, Indexerstellung usw.), wird dies unser Benchmark sein.


Zusammenfassung (bis jetzt)

Ich hoffe, du bist noch bei mir! Der Grund, warum wir diesen Weg eingeschlagen haben, ist, dass die Leistung von Bulk-Insert bei SQLite so stark schwankt und es nicht immer offensichtlich ist, welche Änderungen vorgenommen werden müssen, um unseren Vorgang zu beschleunigen. Unter Verwendung desselben Compilers (und derselben Compiler-Optionen), derselben Version von SQLite und derselben Daten haben wir unseren Code und unsere Verwendung von SQLite so optimiert, dass wir von einem Worst-Case-Szenario von 85 Einsätzen pro Sekunde auf über 96.000 Einsätze pro Sekunde!


CREATE INDEX dann INSERT vs. INSERT dann CREATE INDEX

Bevor wir mit der Messung beginnen SELECT Leistung, wissen wir, dass wir Indizes erstellen werden. In einer der unten stehenden Antworten wurde vorgeschlagen, dass es bei Masseneinfügungen schneller ist, den Index zu erstellen, nachdem die Daten eingefügt wurden (im Gegensatz zur Erstellung des Indexes und dem anschließenden Einfügen der Daten). Versuchen wir es:

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

Importierte 864913 Datensätze in 18,13 Sekunden

Daten einfügen und 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);

Importierte 864913 Datensätze in 13,66 Sekunden

Wie erwartet, sind Masseneinfügungen langsamer, wenn eine Spalte indiziert ist, aber es macht einen Unterschied, wenn der Index nach dem Einfügen der Daten erstellt wird. Unser Basiswert ohne Index liegt bei 96.000 Einfügungen pro Sekunde. Wenn wir zuerst den Index erstellen und dann die Daten einfügen, erhalten wir 47.700 Einfügungen pro Sekunde, während wir 63.300 Einfügungen pro Sekunde erhalten, wenn wir zuerst die Daten einfügen und dann den Index erstellen.


Ich nehme gerne Vorschläge für andere Szenarien entgegen, die ich ausprobieren möchte... Und ich werde demnächst ähnliche Daten für SELECT-Abfragen zusammenstellen.

0 Stimmen

Sie müssen im Benchmark die Option "Threading in SQLite aktiviert" oder "nicht aktiviert" hinzufügen, da die Aktivierung von Threads in SQLite Auswirkungen auf die Leistung hat.

1 Stimmen

Warum lassen Sie den Code nicht ohne die Einfügungen laufen, um zu sehen, was die "theoretische" Basislinie ist. Dies kann in mehreren Schritten erfolgen: nur Lesen von Daten und Parsing. Nur Lesen von Daten, Parsen und Binden.

1 Stimmen

Ein weiterer Punkt ist die Größe der Datenbankdatei - die physische Datei, in der die Datenbank die Tabellendaten speichert. Vergewissern Sie sich, dass genügend Speicherplatz für alle Daten, die Sie einfügen wollen, reserviert ist. Auf diese Weise vermeiden Sie automatische Wachstumsoperationen bei Einfügungen. Dasselbe gilt für das Transaktionsprotokoll, in dem alle Änderungen gespeichert werden. Vergewissern Sie sich, dass auch hier genügend Platz vorhanden ist - ein Backup der Datenbank vor dem Einfügen leert normalerweise das Transaktionsprotokoll.

53voto

malkia Punkte 1390

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

Suchen Sie zunächst die Posten in der Tabelle:

SELECT COUNT(*) FROM table

dann die Seiten einlesen (LIMIT/OFFSET):

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

wobei und pro Thread berechnet werden, wie folgt:

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

für jeden Faden:

int offset = thread_index * limit

Für unsere kleine Datenbank (200 MB) brachte dies eine Geschwindigkeitssteigerung von 50-75 % (3.8.0.2 64-Bit auf Windows 7). Unsere Tabellen sind stark nicht-normalisiert (1000-1500 Spalten, etwa 100.000 oder mehr Zeilen).

Zu viele oder zu wenige Threads reichen nicht aus, Sie müssen sich selbst bewerten und ein Profil erstellen.

Da SHAREDCACHE bei uns die Leistung verlangsamt hat, habe ich PRIVATECACHE manuell eingestellt (weil es bei uns global aktiviert war)

36voto

anefeletos Punkte 605

Ich konnte keinen Nutzen aus den Transaktionen ziehen, bis ich cache_size auf einen höheren Wert erhöht habe, d.h. PRAGMA cache_size=10000;

2 Stimmen

Beachten Sie, dass die Verwendung eines positiven Wertes für cache_size setzt die Anzahl der zu cachenden Seiten und nicht die Gesamtgröße des RAM. Mit der Standard-Seitengröße von 4kB fasst diese Einstellung bis zu 40MB Daten pro geöffneter Datei (oder pro Prozess, wenn er mit gemeinsamer Cache ).

0 Stimmen

Das hat definitiv einen Unterschied gemacht

28voto

Jimmy_A Punkte 187

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 etwa 30 Millionen Datensätze. Ich verwende dieselbe Konfiguration, die Sie vorschlagen, aber die Anzahl der INSERTs pro Sekunde ist sehr gering (~10.000 Datensätze pro Sekunde).

Hier versagt Ihr Vorschlag. Sie verwenden eine einzige Transaktion für alle Datensätze und eine einzige Einfügung ohne Fehler. Nehmen wir an, Sie teilen jeden Datensatz in mehrere Einfügungen in verschiedenen Tabellen auf. Was passiert, wenn der Datensatz fehlerhaft ist?

Der ON CONFLICT-Befehl gilt nicht, denn 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, dann müssen alle vorherigen 4 Einfügungen ebenfalls verschwinden.

Hier kommt also das Rollback. Das einzige Problem beim Rollback ist, dass Sie alle Ihre Einfügungen verlieren und von vorne beginnen müssen. Wie kann man das lösen?

Meine Lösung war die Verwendung von mehrere Transaktionen. Ich beginne und beende eine Transaktion alle 10.000 Datensätze (Fragen Sie nicht, warum diese Zahl, es war die schnellste, die ich getestet habe). Ich habe ein Array mit der Größe 10.000 erstellt und füge die erfolgreichen Datensätze dort ein. Wenn der Fehler auftritt, mache ich ein Rollback, beginne eine Transaktion, füge die Datensätze aus meinem Array ein, bestätige und beginne dann eine neue Transaktion nach dem fehlerhaften Datensatz.

Mit dieser Lösung konnte ich die Probleme umgehen, die ich beim Umgang mit Dateien mit fehlerhaften/duplizierten Datensätzen habe (ich hatte fast 4 % fehlerhafte Datensätze).

Der von mir entwickelte Algorithmus half mir, meinen Prozess um 2 Stunden zu verkürzen. Der endgültige Ladevorgang der Datei dauerte 1 Stunde 30 Minuten, was zwar immer noch langsam ist, aber nicht im Vergleich zu den 4 Stunden, die er ursprünglich dauerte. Es gelang mir, die Einfügevorgänge von 10.000/s auf ~14.000/s zu beschleunigen.

Wenn jemand eine andere Idee hat, wie man das Ganze beschleunigen kann, bin ich offen für Vorschläge.

UPDATE :

Zusätzlich zu meiner obigen Antwort sollten Sie bedenken, dass die Anzahl der Einschübe pro Sekunde auch von der Festplatte abhängt, die Sie verwenden. Ich habe es auf 3 verschiedenen PCs mit unterschiedlichen Festplatten getestet und massive Unterschiede in den Zeiten erhalten. PC1 (1h 30m), PC2 (6h) PC3 (14h), also habe ich mich gefragt, woran das liegen könnte.

Nach zwei Wochen der Recherche und der Überprüfung mehrerer Quellen: Festplatte, Ram, Cache, habe ich herausgefunden, dass einige Einstellungen auf Ihrer Festplatte die E/A-Rate beeinflussen können. Wenn Sie auf der Registerkarte "Allgemein" auf "Eigenschaften" klicken, sehen Sie zwei Optionen. Option1: Dieses Laufwerk komprimieren, Opt2: Erlauben, dass der Inhalt von Dateien auf diesem Laufwerk indiziert wird.

Durch die Deaktivierung dieser beiden Optionen benötigen nun alle 3 PCs ungefähr die gleiche Zeit zum Beenden (1 Stunde und 20 bis 40 Minuten). Wenn Sie langsame Einfügungen feststellen, überprüfen Sie, ob Ihre Festplatte mit diesen Optionen konfiguriert ist. Das erspart Ihnen viel Zeit und Kopfzerbrechen bei der Suche nach der Lösung.

1 Stimmen

Ich möchte Folgendes vorschlagen. * Verwenden Sie SQLITE_STATIC vs. SQLITE_TRANSIENT, um eine String-Kopie zu vermeiden, müssen Sie sicherstellen, dass der String nicht geändert wird, bevor die Transaktion ausgeführt wird * Verwenden Sie Bulk Insert INSERT INTO stop_times VALUES (NULL, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),(NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?) * mmap die Datei, um die Anzahl der Syscalls zu reduzieren.

3 Stimmen

Auf diese Weise kann ich 5.582.642 Datensätze in 11,51 Sekunden importieren.

18voto

doesnt_matter Punkte 111

Die Antwort auf Ihre Frage ist, dass das neuere SQLite 3 eine bessere Leistung hat, verwenden Sie es.

Diese Antwort Warum ist ein SQLAlchemy-Insert mit Sqlite 25 Mal langsamer als mit Sqlite3 direkt? von SqlAlchemy Orm Author hat 100k Einfügungen in 0,5 Sekunden, und ich habe ähnliche Ergebnisse mit python-sqlite und SqlAlchemy gesehen. Das führt mich zu der Annahme, dass sich die Leistung mit SQLite 3 verbessert hat.

2voto

stonux Punkte 11

Die Aufgabe in mehrere Transaktionen aufzuteilen, wie es @Jimmy_A getan hat, ist der richtige Weg. Andernfalls können Sie Ihr RAM mit einer Monstertransaktion und einer schweren COMMIT-Aufgabe sättigen.

Zur weiteren Leistungsoptimierung können Sie auch den Write-Back-Cache auf Ihrer Festplatte aktivieren, wenn Sie ein irgendwie batteriegestütztes System verwenden (Laptop, USV, RAID-Controller mit Batterie...).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X