Gleichzeitiger SQLite-Zugriff


177

Behandelt SQLite3 den gleichzeitigen Zugriff durch mehrere Prozesse, die aus derselben Datenbank lesen / schreiben, sicher? Gibt es Plattformausnahmen?


3
Ich habe vergessen, das Kopfgeldziel zu erwähnen: Die meisten Antworten sagen, dass es in Ordnung ist: "SQLite ist schnell genug", "SQLite handhabt Parallelität gut" usw., aber imho, antworte nicht im Detail / erkläre nicht klar, was passiert, wenn zwei Schreibvorgänge stattfinden würde genau zur gleichen Zeit ankommen (theoretischer sehr seltener Fall). 1) Würde es einen Fehler auslösen und das Programm unterbrechen? oder 2) Würde der zweite Schreibvorgang warten, bis der erste abgeschlossen ist? oder 3) Würde einer der Schreibvorgänge verworfen (Datenverlust!)? 4) Noch etwas? In vielen Situationen kann es hilfreich sein, die Einschränkungen des gleichzeitigen Schreibens zu kennen.
Basj

7
@Basj Kurz gesagt, 2) es wird mehrmals warten und es erneut versuchen (konfigurierbar), 1) einen Fehler auslösen, SQLITE_BUSY.3) Sie können einen Rückruf registrieren, um SQLITE_BUSY-Fehler zu behandeln.
obgnaw

Antworten:


112

Wenn die meisten dieser gleichzeitigen Zugriffe gelesen werden (z. B. SELECT), kann SQLite sie sehr gut verarbeiten. Wenn Sie jedoch gleichzeitig mit dem Schreiben beginnen, kann ein Sperrenkonflikt zu einem Problem werden. Viel hängt dann davon ab, wie schnell Ihr Dateisystem ist, da die SQLite-Engine selbst extrem schnell ist und viele clevere Optimierungen aufweist, um Konflikte zu minimieren. Besonders SQLite 3.

Für die meisten Desktop- / Laptop- / Tablet- / Telefonanwendungen ist SQLite schnell genug, da nicht genügend Parallelität besteht. (Firefox verwendet SQLite häufig für Lesezeichen, Verlauf usw.)

Für Serveranwendungen hat vor einiger Zeit jemand gesagt, dass weniger als 100.000 Seitenaufrufe pro Tag in typischen Szenarien (z. B. Blogs, Foren) von einer SQLite-Datenbank perfekt verarbeitet werden können, und ich habe noch keine gegenteiligen Beweise gefunden. Tatsächlich würden mit modernen Festplatten und Prozessoren 95% der Websites und Webdienste mit SQLite einwandfrei funktionieren.

Wenn Sie einen sehr schnellen Lese- / Schreibzugriff wünschen, verwenden Sie eine speicherinterne SQLite-Datenbank . RAM ist mehrere Größenordnungen schneller als Festplatte.


16
OP fragt nicht nach Effizienz und Geschwindigkeit, sondern nach gleichzeitigem Zugriff. Webserver haben nichts damit zu tun. Gleiches gilt für die Speicherdatenbank.
Jarekczek

1
Sie haben bis zu einem gewissen Grad Recht, aber Effizienz / Geschwindigkeit spielen eine Rolle. Schnellere Zugriffe bedeuten, dass weniger Zeit für das Warten auf Sperren aufgewendet wird, wodurch die Nachteile der Parallelitätsleistung von SQLite verringert werden. Insbesondere wenn Sie nur wenige und schnelle Schreibvorgänge haben, scheint die Datenbank für einen Benutzer überhaupt keine Parallelitätsprobleme zu haben.
Caboose

1
Wie würden Sie den gleichzeitigen Zugriff auf eine speicherinterne SQLite-Datenbank verwalten?
P-Gn

42

Ja, SQLite verarbeitet Parallelität gut, aber unter Leistungsgesichtspunkten ist es nicht das Beste. Soweit ich das beurteilen kann, gibt es keine Ausnahmen. Die Details finden Sie auf der SQLite-Website: https://www.sqlite.org/lockingv3.html

Diese Aussage ist von Interesse: "Das Pager-Modul stellt sicher, dass Änderungen auf einmal vorgenommen werden, dass entweder alle Änderungen vorgenommen werden oder keine, dass zwei oder mehr Prozesse nicht gleichzeitig versuchen, auf inkompatible Weise auf die Datenbank zuzugreifen."


2
Hier sind einige Kommentare zu Problemen auf verschiedenen Plattformen , nämlich NFS-Dateisystemen und Windows (obwohl dies möglicherweise nur alte Versionen von Windows
Nate

1
Ist es möglich, eine SQLite3-Datenbank in den Arbeitsspeicher zu laden, die für alle Benutzer in PHP verwendet werden soll? Ich
vermute

1
@foxyfennec .. ein Ausgangspunkt, obwohl SQLite möglicherweise nicht die optimale Datenbank für diesen Anwendungsfall ist. sqlite.org/inmemorydb.html
kingPuppy

37

Ja tut es. Lassen Sie uns herausfinden, warum

SQLite ist eine Transaktion

Alle Änderungen innerhalb einer einzelnen Transaktion in SQLite erfolgen entweder vollständig oder gar nicht

Diese ACID-Unterstützung sowie das gleichzeitige Lesen / Schreiben werden auf zwei Arten bereitgestellt: mithilfe des sogenannten Journaling (nennen wir es " alte Methode ") oder der Vorausschreibprotokollierung (nennen wir es " neue Methode") ").

Journaling (Old Way)

In diesem Modus verwendet SQLite die Sperre DATABASE-LEVEL . Dies ist der entscheidende Punkt zu verstehen.

Das heißt, wann immer es etwas lesen / schreiben muss, erhält es zuerst eine Sperre für das GESAMTE Datenbankdatei. Mehrere Leser können nebeneinander existieren und etwas parallel lesen

Während des Schreibens wird sichergestellt, dass eine exklusive Sperre erworben wird und keine andere Prozess gleichzeitig liest / schreibt, und daher sind Schreibvorgänge sicher.

Aus diesem Grund heißt es hier , dass SQlite serialisierbare Transaktionen implementiert

Probleme

Da jedes Mal eine gesamte Datenbank gesperrt werden muss und jeder auf einen Prozess wartet, bei dem das Schreiben von Parallelität leidet, leiden solche gleichzeitigen Schreib- / Lesevorgänge von relativ geringer Leistung

Rollbacks / Ausfälle

Bevor SQLite etwas in die Datenbankdatei schreibt, speichert es zunächst den zu ändernden Block in einer temporären Datei. Wenn während des Schreibens in die Datenbankdatei etwas abstürzt, wird diese temporäre Datei abgerufen und die Änderungen werden rückgängig gemacht

Write-Ahead-Protokollierung oder WAL (New Way)

In diesem Fall werden alle Schreibvorgänge an eine temporäre Datei angehängt ( Write-Ahead-Protokoll) ) und diese Datei wird regelmäßig mit der ursprünglichen Datenbank zusammengeführt. Wenn SQLite nach etwas sucht, überprüft es zuerst diese temporäre Datei. Wenn nichts gefunden wird, fahren Sie mit der Hauptdatenbankdatei fort.

Infolgedessen konkurrieren die Leser nicht mit Schriftstellern, und die Leistung ist im Vergleich zum Old Way viel besser.

Vorsichtsmaßnahmen

SQlite hängt stark von der zugrunde liegenden Sperrfunktion des Dateisystems ab, daher sollte es mit Vorsicht verwendet werden. Weitere Details finden Sie hier

Es ist auch wahrscheinlich, dass Sie auf einen gesperrten Datenbankfehler stoßen, insbesondere im Journalmodus. Daher muss Ihre App unter Berücksichtigung dieses Fehlers entworfen werden


34

Niemand scheint den WAL-Modus (Write Ahead Log) erwähnt zu haben. Stellen Sie sicher, dass die Transaktionen ordnungsgemäß organisiert sind und der WAL-Modus aktiviert ist. Sie müssen die Datenbank nicht gesperrt halten, während Benutzer während eines Updates Dinge lesen.

Das einzige Problem ist, dass die WAL irgendwann wieder in die Hauptdatenbank aufgenommen werden muss, und dies geschieht, wenn die letzte Verbindung zur Datenbank geschlossen wird. Bei einer stark ausgelasteten Site kann es einige Sekunden dauern, bis alle Verbindungen geschlossen sind, aber 100.000 Treffer pro Tag sollten kein Problem sein.


Interessant, funktioniert aber nur auf einem einzelnen Computer, nicht auf Szenarien, auf die über ein Netzwerk auf die Datenbank zugegriffen wird.
Bobík

Es ist erwähnenswert, dass das Standardzeitlimit für das Warten eines Schriftstellers 5 Sekunden beträgt und nach diesem database is lockedFehler vom Verfasser
ausgelöst wird

16

Im Jahr 2019 gibt es zwei neue Optionen für das gleichzeitige Schreiben, die noch nicht veröffentlicht wurden, aber in separaten Filialen verfügbar sind.

"PRAGMA journal_mode = wal2"

Der Vorteil dieses Journalmodus gegenüber dem regulären "Wal" -Modus besteht darin, dass Autoren weiterhin in eine Wal-Datei schreiben können, während die andere mit einem Prüfpunkt versehen ist.

CONCURRENT BEGINNEN - Link zum detaillierten Dokument

Mit der Erweiterung BEGIN CONCURRENT können mehrere Writer Schreibtransaktionen gleichzeitig verarbeiten, wenn sich die Datenbank im Modus "wal" oder "wal2" befindet, obwohl das System weiterhin COMMIT-Befehle serialisiert.

Wenn eine Schreibtransaktion mit "BEGIN CONCURRENT" geöffnet wird, wird das tatsächliche Sperren der Datenbank verschoben, bis ein COMMIT ausgeführt wird. Dies bedeutet, dass eine beliebige Anzahl von Transaktionen, die mit BEGIN CONCURRENT gestartet wurden, gleichzeitig ausgeführt werden können. Das System verwendet eine optimistische Sperre auf Seitenebene, um zu verhindern, dass widersprüchliche gleichzeitige Transaktionen festgeschrieben werden.

Zusammen sind sie in begin-concurrent-wal2 oder jeweils in einem eigenen Zweig vorhanden .


1
Haben wir eine Idee, wann diese Funktionen in die Release-Version aufgenommen werden? Sie könnten mir wirklich nützlich sein.
Peter Moore

2
Keine Ahnung. Sie könnten leicht aus den Zweigen bauen. Für .NET habe ich eine Bibliothek mit Low-Level-Schnittstelle & WAL2 + beginne gleichzeitig + FTS5: github.com/Spreads/Spreads.SQLite
VB

Oh, sicher danke. Ich wundere mich mehr über Stabilität. SQLite ist ziemlich erstklassig, wenn es um ihre Releases geht, aber ich weiß nicht, wie riskant es wäre, einen Zweig im Produktionscode zu verwenden.
Peter Moore

2
Siehe diesen Thread github.com/Expensify/Bedrock/issues/65 und Bedrock im Allgemeinen. Sie benutzen es in der Produktion und haben das begin concurrentZeug gepusht .
VB

13

SQLite verfügt über eine Readers-Writer-Sperre auf Datenbankebene. Mehrere Verbindungen (möglicherweise im Besitz verschiedener Prozesse) können gleichzeitig Daten aus derselben Datenbank lesen, aber nur eine kann in die Datenbank schreiben.

SQLite unterstützt eine unbegrenzte Anzahl gleichzeitiger Leser, erlaubt jedoch zu jedem Zeitpunkt nur einen Schreiber. In vielen Situationen ist dies kein Problem. Warteschlange für Autoren. Jede Anwendung erledigt ihre Datenbankarbeit schnell und geht weiter, und keine Sperre dauert länger als ein paar Dutzend Millisekunden. Es gibt jedoch einige Anwendungen, die mehr Parallelität erfordern, und diese Anwendungen müssen möglicherweise nach einer anderen Lösung suchen. - Geeignete Verwendungen für SQLite @ SQLite.org

Die Readers-Writer-Sperre ermöglicht eine unabhängige Transaktionsverarbeitung und wird mithilfe exklusiver und gemeinsam genutzter Sperren auf Datenbankebene implementiert.

Eine exklusive Sperre muss eingeholt werden, bevor eine Verbindung eine Schreiboperation für eine Datenbank ausführt. Nachdem die exklusive Sperre erhalten wurde, werden sowohl Lese- als auch Schreibvorgänge von anderen Verbindungen blockiert, bis die Sperre wieder aufgehoben wird.

Implementierungsdetails für den Fall gleichzeitiger Schreibvorgänge

SQLite verfügt über eine Sperrtabelle, mit deren Hilfe die Datenbank während eines Schreibvorgangs so spät wie möglich gesperrt werden kann, um maximale Parallelität zu gewährleisten.

Der Anfangszustand ist UNLOCKED, und in diesem Zustand hat die Verbindung noch nicht auf die Datenbank zugegriffen. Wenn ein Prozess mit einer Datenbank verbunden ist und sogar eine Transaktion mit BEGIN gestartet wurde, befindet sich die Verbindung immer noch im Status UNLOCKED.

Nach dem Status UNLOCKED ist der nächste Status der Status SHARED. Um Daten aus der Datenbank lesen (nicht schreiben) zu können, muss die Verbindung zuerst in den Status SHARED versetzt werden, indem eine SHARED-Sperre aktiviert wird. Mehrere Verbindungen können gleichzeitig gemeinsam genutzte Sperren erhalten und verwalten, sodass mehrere Verbindungen gleichzeitig Daten aus derselben Datenbank lesen können. Solange jedoch nur eine SHARED-Sperre nicht freigegeben ist, kann keine Verbindung einen Schreibvorgang in die Datenbank erfolgreich abschließen.

Wenn eine Verbindung in die Datenbank schreiben möchte, muss sie zuerst eine RESERVIERTE Sperre erhalten.

Es kann immer nur eine einzige RESERVIERTE Sperre gleichzeitig aktiv sein, obwohl mehrere gemeinsam genutzte Sperren mit einer einzelnen RESERVIERTEN Sperre koexistieren können. RESERVIERT unterscheidet sich von PENDING dadurch, dass neue SHARED-Sperren erworben werden können, während eine RESERVIERTE Sperre vorhanden ist. - Dateisperrung und Parallelität in SQLite Version 3 @ SQLite.org

Sobald eine Verbindung eine RESERVIERTE Sperre erhält, kann sie mit der Verarbeitung von Datenbankänderungsvorgängen beginnen. Diese Änderungen können jedoch nur im Puffer vorgenommen werden, anstatt tatsächlich auf die Festplatte geschrieben zu werden. Die am Ausleseinhalt vorgenommenen Änderungen werden im Speicherpuffer gespeichert. Wenn eine Verbindung eine Änderung (oder Transaktion) senden möchte, muss die RESERVIERTE Sperre auf eine EXKLUSIVE Sperre aktualisiert werden. Um das Schloss zu erhalten, müssen Sie das Schloss zuerst auf ein PENDING-Schloss heben.

Eine PENDING-Sperre bedeutet, dass der Prozess, der die Sperre hält, so schnell wie möglich in die Datenbank schreiben möchte und nur darauf wartet, dass alle aktuellen SHARED-Sperren gelöscht werden, damit er eine EXKLUSIVE Sperre erhält. Es sind keine neuen SHARED-Sperren für die Datenbank zulässig, wenn eine PENDING-Sperre aktiv ist, obwohl vorhandene SHARED-Sperren fortgesetzt werden dürfen.

Eine EXKLUSIVE Sperre ist erforderlich, um in die Datenbankdatei zu schreiben. Es ist nur eine EXKLUSIVE Sperre für die Datei zulässig, und es dürfen keine anderen Sperren jeglicher Art mit einer EXKLUSIVEN Sperre koexistieren. Um die Parallelität zu maximieren, minimiert SQLite die Zeit, die EXKLUSIVE Sperren gehalten werden. - Dateisperrung und Parallelität in SQLite Version 3 @ SQLite.org

Sie können also sagen, dass SQLite den gleichzeitigen Zugriff durch mehrere Prozesse, die in dieselbe Datenbank schreiben, sicher verarbeitet, nur weil es dies nicht unterstützt! Sie erhalten SQLITE_BUSYoder SQLITE_LOCKEDfür den zweiten Schreiber, wenn er die Wiederholungsbeschränkung erreicht.


Danke dir. Ein Beispiel für Code mit 2 Autoren wäre sehr gut, um zu verstehen, wie es funktioniert.
Basj

1
@Basj, kurz gesagt, SQLite hat eine Lese- / Schreibsperre für die Datenbankdatei. Dies entspricht dem gleichzeitigen Schreiben einer Datei. Und mit WAL kann immer noch nicht gleichzeitig geschrieben werden, aber WAL kann das Schreiben beschleunigen und Lesen und Schreiben können gleichzeitig erfolgen. Und WAL führt eine neue Sperre wie WAL_READ_LOCK, WAL_WRITE_LOCK ein.
obgnaw

2
Könnten Sie eine Warteschlange verwenden und mehrere Threads die Warteschlange füttern lassen und nur einen Thread mithilfe der SQL-Anweisungen in der Warteschlange in die Datenbank schreiben? So etwas wie diese
Gabriel

7

Dieser Thread ist alt, aber ich denke, es wäre gut, das Ergebnis meiner auf SQLite durchgeführten Tests zu teilen: Ich habe 2 Instanzen des Python-Programms (verschiedene Prozesse, dasselbe Programm) ausgeführt, wobei die Anweisungen SELECT und UPDATE SQL-Befehle innerhalb der Transaktion ausgeführt wurden, wobei EXCLUSIVE lock und Timeout auf gesetzt waren 10 Sekunden, um eine Sperre zu erhalten, und das Ergebnis war frustrierend. Jede Instanz hat in 10000 Schrittschleifen:

  • Verbindung mit DB mit exklusiver Sperre
  • Wählen Sie in einer Zeile den Zähler aus
  • Aktualisieren Sie die Zeile mit einem neuen Wert, der dem um 1 erhöhten Zähler entspricht
  • enge Verbindung zu db

Selbst wenn SQLite eine exklusive Sperre für Transaktionen gewährt hat, war die Gesamtzahl der tatsächlich ausgeführten Zyklen nicht gleich 20 000, sondern geringer (Gesamtzahl der Iterationen über einen einzelnen Zähler, die für beide Prozesse gezählt wurden). Das Python-Programm hat fast keine einzige Ausnahme ausgelöst (nur einmal während der Auswahl für 20 Ausführungen). Die SQLite-Revision zum Zeitpunkt des Tests war 3.6.20 und Python v3.3 CentOS 6.5. Meiner Meinung nach ist es besser, ein zuverlässigeres Produkt für diese Art von Arbeit zu finden oder Schreibvorgänge auf SQLite auf einen einzelnen Prozess / Thread zu beschränken.


5
Es sieht so aus, als müssten Sie einige magische Wörter sagen, um eine Sperre für Python zu erhalten, wie hier beschrieben: stackoverflow.com/a/12848059/1048959 Dies trotz der Tatsache, dass die Python-SQLite-Dokumentation Sie glauben lässt, dass dies with conausreicht.
Dan Stahlke
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.