SQLite ist eine beliebte relationale Datenbank, die Sie in Ihre Anwendung einbetten. Mit einer zunehmenden Datenmenge in Ihrer Datenbank müssen Sie die SQLite-Leistungsoptimierung anwenden. Dieser Artikel behandelt Indizes und ihre Fallstricke, die Verwendung des Abfrageplaners, den Journalmodus Write-Ahead-Logging (WAL) und die Erhöhung der Cache-Größe. Außerdem wird erläutert, wie wichtig es ist, die Auswirkungen Ihrer Optimierungen mithilfe automatisierter Tests zu messen.
Einführung
SQLite ist ein beliebtes relationales Datenbanksystem (DB) . Im Gegensatz zu seinen größeren Client-Server-basierten Brüdern wie MySQL kann SQLite als Bibliothek in Ihre Anwendung eingebettet werden . SQLite hat einen sehr ähnlichen Funktionsumfang und kann auch Millionen von Zeilen verarbeiten, vorausgesetzt, Sie kennen einige Tipps und Tricks zur Leistungsoptimierung. Wie die folgenden Abschnitte zeigen werden, gibt es mehr zu wissen über die SQLite-Leistungsoptimierung als nur das Erstellen von Indizes.
Erstellen Sie Indizes, aber mit Vorsicht
Die grundlegende Idee eines Index ist es, das Lesen zu beschleunigen bestimmter Daten , also SELECT
Anweisungen mit einem WHERE
Klausel. Indizes beschleunigen auch das Sortieren Daten (ORDER BY
) oder JOIN
Tabellen. Leider sind Indizes ein zweischneidiges Schwert, da sie zusätzlichen Speicherplatz verbrauchen und die Datenmanipulation verlangsamen (INSERT
, UPDATE
, DELETE
).
Der allgemeine Rat lautet, so wenige Indizes wie möglich, aber so viele wie nötig zu erstellen . Außerdem machen Indizes nur für größere Sinn Datenbanken mit Tausenden oder Millionen von Zeilen.
Verwenden Sie den Abfrageplaner, um Ihre Abfragen zu analysieren
Die Art und Weise, wie Indizes intern von SQLite verwendet werden, ist dokumentiert, aber nicht sehr einfach zu verstehen. Wie in diesem Artikel weiter ausgeführt, ist es eine gute Idee, eine Abfrage zu analysieren, indem Sie ihr EXPLAIN QUERY PLAN
voranstellen . Schauen Sie sich jede Ausgabezeile an, von der es drei grundlegende Varianten gibt:
SEARCH table ...
Zeilen sind ein gutes Zeichen – SQLite verwendet einen Ihrer Indizes!SCAN table ... USING INDEX
ist ein schlechtes Zeichen,SCAN table ...
ist noch schlimmer!
Versuchen Sie, SCAN table [using index]
zu vermeiden Einträge in der Ausgabe von EXPLAIN QUERY PLAN
wann immer möglich, da Sie bei größeren Datenbanken auf Leistungsprobleme stoßen werden. Verwenden Sie EXPLAIN QUERY PLAN
um Ihre Indizes iterativ hinzuzufügen oder zu ändern, bis keine SCAN table
mehr vorhanden ist Einträge erscheinen.
Optimieren Sie Abfragen, die IS NOT
beinhalten
Die Suche nach IS NOT ...
ist teuer da SQLite scannen muss alle Zeilen der Tabelle, auch wenn die betroffene Spalte einen Index hat . Indizes sind nur sinnvoll, wenn Sie nach bestimmten Werten suchen, also Vergleiche mit < (kleiner), > (größer) oder = (gleich), aber sie gelten nicht für !=(ungleich).
Ein netter kleiner Trick ist, dass Sie WHERE column != value
ersetzen können mit WHERE column > value OR column < value
. Dies verwendet den Index der Spalte und wirkt sich effektiv auf alle Zeilen aus, deren Wert nicht gleich value
ist . Ebenso ein WHERE stringColumn != ''
kann durch WHERE stringColumn > ''
ersetzt werden , weil Zeichenfolgen sortierbar sind. Stellen Sie bei der Anwendung dieses Tricks jedoch sicher, dass Sie wissen, wie SQLite mit NULL
umgeht Vergleiche. Beispielsweise wertet SQLite NULL > ''
aus als FALSE
.
Wenn Sie einen solchen Vergleichstrick verwenden, gibt es eine weitere Einschränkung, falls Ihre Abfrage WHERE
enthält und ORDER BY
, jeweils mit einer anderen Spalte:Dadurch wird die Abfrage wieder ineffizient. Verwenden Sie nach Möglichkeit dasselbe Spalte in WHERE
und ORDER BY
, oder erstellen Sie einen überdeckenden Index das betrifft sowohl das WHERE
und ORDER BY
Spalte.
Verbessern Sie die Schreibgeschwindigkeit mit dem Write-Ahead-Log
Das Write-Ahead-Logging (WAL) Der Journalmodus verbessert die Schreib-/Aktualisierungsleistung erheblich , im Vergleich zum standardmäßigen Rollback Journalmodus. Wie hier dokumentiert, gibt es jedoch ein paar Vorbehalte . Beispielsweise ist der WAL-Modus auf bestimmten Betriebssystemen nicht verfügbar. Außerdem gibt es reduzierte Datenkonsistenzgarantien im Falle eines Hardwarefehlers . Lesen Sie unbedingt die ersten paar Seiten, um zu verstehen, was Sie tun.
Ich habe festgestellt, dass der Befehl PRAGMA synchronous = NORMAL
bietet eine 3-4x Beschleunigung. Einstellen von journal_mode
zu WAL
verbessert dann die Performance nochmals deutlich (ca. 10x oder mehr, je nach Betriebssystem).
Abgesehen von den bereits erwähnten Einschränkungen sollten Sie auch Folgendes beachten:
- Wenn Sie den WAL-Journalmodus verwenden, befinden sich neben der Datenbankdatei in Ihrem Dateisystem zwei zusätzliche Dateien, die den gleichen Namen wie die Datenbank haben, jedoch mit den Endungen „-shm“ und „-wal“. Normalerweise müssen Sie sich nicht darum kümmern, aber wenn Sie die Datenbank an einen anderen Computer senden, während Ihre Anwendung läuft, vergessen Sie nicht, diese beiden Dateien einzuschließen. SQLite wird diese beiden Dateien immer dann in die Hauptdatei komprimieren, wenn Sie normalerweise alle offenen Datenbankverbindungen geschlossen haben.
- Die Einfüge- oder Aktualisierungsleistung sinkt gelegentlich, wenn die Abfrage das Zusammenführen des Inhalts der WAL-Protokolldatei mit der Hauptdatenbankdatei auslöst. Dies wird als Checkpointing bezeichnet , siehe hier.
- Ich habe dieses
PRAGMA
gefunden s, diejournal_mode
ändern undsynchronous
scheinen nicht dauerhaft in der Datenbank gespeichert zu sein. Also ich immer führe sie jedes Mal erneut aus, wenn ich eine neue Datenbankverbindung öffne, anstatt sie nur auszuführen, wenn ich die Tabellen zum ersten Mal erstelle.
Alles messen
Wenn Sie Leistungsoptimierungen vornehmen, achten Sie darauf, die Auswirkungen zu messen. Automatisierte (Einheiten-)Tests sind hierfür ein hervorragender Ansatz. Sie helfen beim Dokumentieren Ihre Ergebnisse für Ihr Team, und sie werden im Laufe der Zeit abweichendes Verhalten aufdecken , z.B. wenn Sie auf eine neuere SQLite-Version aktualisieren. Beispiele für das, was Sie messen können:
- Welchen Effekt hat die Verwendung der WAL Journalmodus über das Rollback Modus? Welche Wirkung haben andere (angeblich) leistungssteigernde
PRAGMA
s? - Wenn Sie einen Index hinzufügen/ändern/entfernen, wie viel schneller
SELECT
Aussagen werden? Wie viel langsamer istINSERT/DELETE/UPDATE
Aussagen werden? - Wie viel zusätzlichen Speicherplatz verbrauchen die Indizes?
Erwägen Sie für jeden dieser Tests, ihn mit unterschiedlichen Datenbankgrößen zu wiederholen. Z.B. Führen Sie sie auf einer leeren Datenbank aus und auch auf einer Datenbank, die bereits Tausende (oder Millionen) von Einträgen enthält. Sie sollten die Tests auch auf verschiedenen Geräten und Betriebssystemen ausführen, insbesondere wenn sich Ihre Entwicklungs- und Produktionsumgebung erheblich unterscheiden.
Stellen Sie die Cache-Größe ein
SQLite speichert temporäre Informationen in einem Cache (im RAM), z.B. beim Erstellen der Ergebnisse eines SELECT
Abfrage oder beim Manipulieren von Daten, die noch nicht festgeschrieben wurden. Standardmäßig beträgt diese Größe schlappe 2 MB . Moderne Desktop-Maschinen können viel mehr ersparen. Führen Sie PRAGMA cache_size = -kibibytes
aus um diesen Wert zu erhöhen (achten Sie auf das Minus Zeichen vor dem Wert!). Weitere Informationen finden Sie hier. Wieder messen welche Auswirkungen diese Einstellung auf die Leistung hat!
Verwenden Sie REPLACE INTO, um eine Zeile zu erstellen oder zu aktualisieren
Dies ist möglicherweise weniger eine Leistungsoptimierung als vielmehr ein netter kleiner Trick. Angenommen, Sie müssen aktualisieren eine Zeile in der Tabelle t
, oder erstellen eine Zeile, falls noch nicht vorhanden. Anstatt zwei Abfragen (SELECT
gefolgt von INSERT
oder UPDATE
), verwenden Sie den REPLACE INTO
(offizielle Dokumente).
Damit dies funktioniert, fügen Sie eine zusätzliche Dummy-Spalte hinzu (z. B. replacer
) in die Tabelle t
, die einen UNIQUE
hat beschränken. Die Deklaration der Spalte könnte z.B. sei ... replacer INTEGER UNIQUE ...
das ist Teil Ihrer CREATE TABLE
Erklärung. Verwenden Sie dann eine Abfrage wie
REPLACE INTO t (col1, col2, ..., replacer) VALUES (?,?,...,1)
Code language: SQL (Structured Query Language) (sql)
Wenn diese Abfrage zum ersten Mal ausgeführt wird, führt sie einfach ein INSERT
aus . Bei der zweiten Ausführung wird der UNIQUE
Beschränkung des replacer
-Spalte wird ausgelöst, und das Konfliktlösungsverhalten bewirkt, dass die alte Zeile gelöscht und automatisch eine neue erstellt wird. Möglicherweise finden Sie auch den zugehörigen UPSERT-Befehl nützlich.
Schlussfolgerung
Sobald die Anzahl der Zeilen in Ihrer Datenbank wächst, werden Leistungsoptimierungen zu einer Notwendigkeit. Indizes sind die häufigste Lösung. Sie tauschen eine verbesserte Zeitkomplexität gegen eine verringerte Platzkomplexität ein, wodurch die Lesegeschwindigkeit verbessert wird, während die Leistung der Datenänderung negativ beeinflusst wird. A Ich habe gezeigt, dass Sie besonders vorsichtig sein müssen, wenn Sie auf Ungleichheit vergleichen in SELECT
-Anweisungen, da SQLite für solche Vergleiche keine Indizes verwenden kann. Ich empfehle generell die Verwendung des Abfrageplaners das erklärt, was intern für jede SQL-Abfrage passiert. Wann immer Sie etwas optimieren, messen Sie die Wirkung!