SQLite - Database is locked/Speicherzugriffsfehler

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Trubinial Guru
User
Beiträge: 117
Registriert: Dienstag 7. April 2009, 13:40

Hallo, ich kämpfe seit über einer Woche mit Problemen, die beim Zugriff auf meine SQLite Datebank auftreten.

Erst einmal habe ich verschiedene Threads und aus jedem Thread soll auf die Datenbank zugegriffen werden können. Deshalb habe ich check_same_thread = False gesetzt. Trotzdem kommt es zu "Database is locked" Fehlermeldungen, weshalb ich autocommit durch "isolation_level=None" aktiviert habe.

Der "Database is locked" Fehler trifft dann nicht mehr auf, aber dafür stürzt das Programm aus dem nichts ab. In der Konsole ist dann nurnoch "Speicherzugriffsfehler" zu lesen.

ich kann mir sehr gut vorstellen, dass sich da verschiedene SQLite Zugriffe in die quere kommen, aber weiß einfach nicht wie ich das lösen kann.
Ich hoffe jemand hat eine Idee
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Oha, du solltest unbedingt aufhören mittels Raten nach funktionierenden Lösungen zu suchen ;-) Im Prinzip stellst du nur alle (berechtigten) Warnungen ab.

Die Lösung ist an sich ganz einfach: du brauchst ein zentrales Objekt, welches sich alleine um die Datenbank kümmert. Dieses musst du dann an die Threads übergeben und die Threads können nur noch über diese Zwischenschicht mit der Datenbank kommunizieren. Die Schnittstelle dieses Zwischenobjekts sorgt dann selbst dafür, dass alle Zugriffe entsprechend gekapselt werden.
Das Leben ist wie ein Tennisball.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Oder in Kurzform: Auf SQL-Datenbanken kann nicht aus mehreren Threads schreibend zugriffen werden, jedenfalls soweit ich weiß.

Wie EyDu schrieb: sorg für eine zentrale Anlaufstelle für alle Zugriffe auf die Datenbank und sorge selbst dafür, dass immer nur ein Thread zugreift.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

> Auf SQL-Datenbanken kann nicht aus mehreren Threads schreibend zugriffen werden, jedenfalls soweit ich weiß.
Jein. SQLite kann es nicht. Andere DBs schon, weil die sich dann selber intern die Schreibzugriffe in eine Abarbeitungsschlange sortieren.

@Trubinial Guru: Willst du aus mehreren Threads schreiben oder lesen? Du schreibst immer nur von "Zugriff" - was ja beides sein kann.

Gruß, noisefloor
deets

Das ist ja eine ziemlich wilde Ansammlung von Halbwahrheiten hier. Um es freundlich zu sagen...

Fakt ist: mit SQLite kann man sehr wohl mit mehreren Threads gleichzeitig lesen und schreiben. Es ist allerdings nicht besonders optimiert dafuer, parallel zu schreiben, und setzt dort recht grobe Locks. So, wie das Python-GIL...

Und die Aussage von sparrow ist natuerlich kompletter Unfug. Groessere DB-Engines wie zB Postgres skalieren durchaus mit Anzahl der Kerne zB. Das hoert sich eher nach NoSQL-Propaganda an...

Und darum sind die hier gegebenen Tipps, dass man den Zugriff auf die DB serialisieren muesste, auch voellig falsch. Wie folgendes kleines Skript sofort verdeutlicht:

Code: Alles auswählen

import sqlite3
import threading
import random
import os

DB = "/tmp/test.db"

if not os.path.exists(DB):
    conn = sqlite3.connect(DB)
    c = conn.cursor()
    #c.execute("""create table test ( foo int)""")
    conn.close()


def work():
    conn = sqlite3.connect(DB)
    c = conn.cursor()
    while True:
        if random.random() > .5:
            v = random.randint(0, 10000)
            c.execute("insert into test values (?)", (v,))
        else:
            c.execute("select count(*) from test")
        conn.commit()


threads = []

for _ in xrange(10):
    t = threading.Thread(target=work)
    t.setDaemon(True)
    t.start()
    threads.append(t)

while True:
    pass
@Trubinalguru: ich vermute dein Problem liegt darin, dass du eine Connection zwischen verschiedenen Threads *teilst*. Das darfst du natuerlich nicht machen, bzw. dann tatsaechlich nur, wenn du die Zugriffe serialisierst. Und das ist im uebrigen auch keine Einschraenkung von SQLite - sowas koennen die wenigen DB-Adapter.
deets

Oh, und jetzt lese ich erst die Anwort von Noisefloor... noch bunter. Und noch falscher. Natuerlich kommt es vor, das Zugriffe serialisiert werden. Aber grosse DB-Engines haben feingranulares locking, welches dafuer sorgt, dass nur dann, wenn es wirklich *konkurrierende* Zugriffe sind, also Zugriffe auf Daten, die in denselben Tabellen bzw. sogar selben Spalten liegen, es zu solchen "harten" Massnahmen kommt.

Wenn Zugriffe disjunkt sind, wird da nix serialisiert...
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@deets: so meinte ich das auch. Dein Erklärung ist aber... besser als meine ;-)

Und noch 'ne Frage:
Es ist allerdings nicht besonders optimiert dafuer, parallel zu schreiben, und setzt dort recht grobe Locks. So, wie das Python-GIL...
Genau, es gib ein globales Lock auf die DB. Aber genau dadurch geht "parallel" doch nicht?

Und eine NoSQL Anmerkung:
Das hoert sich eher nach NoSQL-Propaganda an...
Ja und nein. Es gibt ja auch NoSQL-DBs, die das nicht können.

Da war auch der Hintergrund der Frage an den OP: Wenn man konkurrierende / parallele Schreibzugriffe hat, dann heißt das für mich implizit, dass a) entweder viel oder b) sehr lang geschrieben wird. Und dann möchte man sich evtl. mal nach anderen DBs umschauen - oder den Code bzw. die Tabellenstruktur überdenken.

Gruß, noisefloor
deets

noisefloor hat geschrieben:Hallo,

@deets: so meinte ich das auch. Dein Erklärung ist aber... besser als meine ;-)
Mit Verlaub: deine Erklaerung ist komplett falsch. Bezogen auf SQLite behauptest du, dass man nicht mit mehreren Threads schreibend darauf zugreifen kann. Kann man aber. Und du behauptest weiter, alle *anderen* DBs wuerden Schreibzugriffe zwangsweise serialisieren. Was sie nicht tun.

Das magst du nicht gemeint haben, aber so hast du's nunmal gesagt.
Und noch 'ne Frage:
Es ist allerdings nicht besonders optimiert dafuer, parallel zu schreiben, und setzt dort recht grobe Locks. So, wie das Python-GIL...
Genau, es gib ein globales Lock auf die DB. Aber genau dadurch geht "parallel" doch nicht?
Dass es genau *ein* Lock setzt muss nicht so sein. In Version 3 hat sich da sehr viel getan:

http://www.sqlite.org/lockingv3.html

Was da genau und im Detail wann und wo und im Vergleich zu anderen DBs passiert, kannst du ja selbst rausfinden.
Und eine NoSQL Anmerkung:
Das hoert sich eher nach NoSQL-Propaganda an...
Ja und nein. Es gibt ja auch NoSQL-DBs, die das nicht können.

Da war auch der Hintergrund der Frage an den OP: Wenn man konkurrierende / parallele Schreibzugriffe hat, dann heißt das für mich implizit, dass a) entweder viel oder b) sehr lang geschrieben wird. Und dann möchte man sich evtl. mal nach anderen DBs umschauen - oder den Code bzw. die Tabellenstruktur überdenken.
Deine Implikation ist ebenfalls falsch. Konkurrierende Zugriffe hat zB jede Webseite, schon alleine wenn ein einziger Browser mehrere lesende Verbindungen aufmacht. Und die meisten Webseiten da draussen, auch erfolgreiche und grosse, laufen problemlos *ohne* NoSQL-Loesungen.

Selbst SoundCloud hat bis zu den ersten Millionen Usern noch MySQL eingesetzt... und tut es AFAIK immer noch.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Huch, ich habe mich oben natürlich verschrieben, es meinte natürlich SQLite Datenbanken.

Der Beispielcode von deets bringt hier auch entsprechend viele "OperationalError: database is locked" Fehler.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Konkurrierende Zugriffe hat zB jede Webseite, schon alleine wenn ein einziger Browser mehrere lesende Verbindungen aufmacht. Und die meisten Webseiten da draussen, auch erfolgreiche und grosse, laufen problemlos *ohne* NoSQL-Loesungen.
Lesend ist ja auch selten ein Problem. Deshalb schrieb ich ja auch "schreibend".

Abgesehen davon behaupte ich auch nicht, das NoSQL besser ist. Eine weitere Infos vom OP kann man eigentlich noch überhaupt nicht sagen, welche DB "bessere" wäre- und ob überhaupt. Aber die Infos kommen ja (hoffentlich) noch. :-)

Gruß, noisefloor
deets

noisefloor hat geschrieben:
Konkurrierende Zugriffe hat zB jede Webseite, schon alleine wenn ein einziger Browser mehrere lesende Verbindungen aufmacht. Und die meisten Webseiten da draussen, auch erfolgreiche und grosse, laufen problemlos *ohne* NoSQL-Loesungen.
Lesend ist ja auch selten ein Problem. Deshalb schrieb ich ja auch "schreibend".
Und wo habe ich geschrieben, dass ein lesender Browser-Request nicht auch schreibende Operationen ausloest (logging, user-tracking usw)? Und die meisten Webseiten haben ja sogar noch ein Quantum expliziter Schreib-OPs. Facebook zB setzt AFAIK immer noch MySQL ein. Auch wenn die damit nicht 100%ig gluecklich sind, so hat es sie bis zur ersten 100 Million ganz gut getragen...
deets

sparrow hat geschrieben:Huch, ich habe mich oben natürlich verschrieben, es meinte natürlich SQLite Datenbanken.

Der Beispielcode von deets bringt hier auch entsprechend viele "OperationalError: database is locked" Fehler.
Tut er? Bei mir nicht. Kann sein, dass du eine alte SQLite verwendest?
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

deets hat geschrieben:Tut er? Bei mir nicht. Kann sein, dass du eine alte SQLite verwendest?
Python 2.7.2, das Sqlite 3 kommt da ja mit.
Auf der Seite der Dokumentation, die du verlinkst, steht das auch ziemlich deutlich:
An EXCLUSIVE lock is needed in order to write to the database file. Only one EXCLUSIVE lock is allowed on the file and no other locks of any kind are allowed to coexist with an EXCLUSIVE lock.
Man kann das auch ganz gut nachstellen, ganz ohne Threads. Sobald eine Transaktion schreibt bekommt die Datenbank einen exclusiven Lock. Dann kann keine weitere Vebindung schreiben:

Code: Alles auswählen

Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import sqlite3
>>> DB = r"c:\temp\test.db"
>>> con1 = sqlite3.connect(DB)
>>> con2 = sqlite3.connect(DB)
>>> con1.execute("CREATE TABLE test (nr int, name varchar)")
<sqlite3.Cursor object at 0x02959D20>
>>> con2.execute("SELECT * FROM test")
<sqlite3.Cursor object at 0x02959DE0>
>>> con1.execute("INSERT INTO test (nr, name) VALUES (1, 'hallo')")
<sqlite3.Cursor object at 0x02959E20>
>>> con2.execute("SELECT * FROM test")
<sqlite3.Cursor object at 0x02959DE0>
>>> con1.execute("BEGIN")
<sqlite3.Cursor object at 0x02959D60>
>>> con2.execute("SELECT * FROM test")
<sqlite3.Cursor object at 0x02959DE0>
>>> con1.execute("INSERT INTO test (nr, name) VALUES (2, 'du')")
<sqlite3.Cursor object at 0x02959D60>
>>> con2.execute("SELECT * FROM test")
<sqlite3.Cursor object at 0x02959DE0>
>>> con2.execute("SELECT * FROM test").fetchall()
[(1, u'hallo')]
>>> con2.execute("INSERT INTO test (nr, name) VALUES (3, 'ich')")

Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    con2.execute("INSERT INTO test (nr, name) VALUES (3, 'ich')")
OperationalError: database is locked
Zuletzt geändert von sparrow am Freitag 21. September 2012, 12:49, insgesamt 1-mal geändert.
deets

Du hast recht, nach einiger Zeit kam es bei mir tatsaechlich auch zu Write-Locks. Allerdings sehr selten, und so wie es aussieht, haben die auch keine "fatalen" Auswirkungen - man kann die Operation dann wiederholen. Je nach Transaktionsmodus einer Postgres kann einem das dort aber ja auch passieren.

Und eigentlich ist es schon unueblich genug, dass es in Python ueberhaupt passiert - wegen des GIL. Ich habe gerade zb mein Programm hier unten laufen, und seit ca 10minuten kein DB-Lock.

Code: Alles auswählen


import sqlite3
import threading
import random
import os

DB = "/tmp/test.db"

if not os.path.exists(DB):
    conn = sqlite3.connect(DB)
    c = conn.cursor()
    c.execute("""create table test ( foo int)""")
    conn.close()


def work():
    conn = sqlite3.connect(DB)
    c = conn.cursor()
    successful_writes = 0
    while True:
        if random.random() > .5:
            if successful_writes % 100 == 0:
                print successful_writes
            v = random.randint(0, 10000)
            while True:
                try:
                    c.execute("insert into test values (?)", (v,))
                    successful_writes += 1
                    break
                except:
                    print successful_writes
                    successful_writes = 0
                    print "db was locked, retrying"
                    
        else:
            c.execute("select count(*) from test")
        conn.commit()


threads = []

for _ in xrange(10):
    t = threading.Thread(target=work)
    t.setDaemon(True)
    t.start()
    threads.append(t)

while True:
    pass
Summa summarum: ich denke nicht, dass der OP mit sqlite ernsthafte Probleme bekommen wird, sofern sein Szenario nicht aussergewoehnlich ist. Schon mein Testprogramm ist ja eher ungewoehnlich mit der hohen Frequenz an Operationen, lesend wie schreibend.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

In ein Problem läuft man dann, wenn man Transaktionen hat, die unter Umständen länger laufen, und in denen einmal ein Schreibzugriff vorgenommen wurde. Die Transaktion sperrt dann bis zum Ende die _gesamte_ Datenbank. Wenn man das weiß, kann man natürlich entsprechend planen.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
ich denke nicht, dass der OP mit sqlite ernsthafte Probleme bekommen wird, sofern sein Szenario nicht aussergewoehnlich ist.
Womit wir wieder da sind, was ich oben schon mal gesagt habe: Wir brauchen mehr Infos vom OP.

Gruß, noisefloor
deets

noisefloor hat geschrieben:Hallo,
ich denke nicht, dass der OP mit sqlite ernsthafte Probleme bekommen wird, sofern sein Szenario nicht aussergewoehnlich ist.
Womit wir wieder da sind, was ich oben schon mal gesagt habe: Wir brauchen mehr Infos vom OP.
Na mensch, gut, dass wir doch noch was gefunden haben, was du richtig gesagt hast, gell? :roll:
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

genau. Und eigentlich wollte ich genau das von dir bestätigt haben :roll:

So, und jetzt poste ich hier nicht mehr, bevor... ihr wisst schon. :wink:

Gruss, noisefloor
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Kinder, mal etwas runterkommen. Gilt für alle Beteiligten. ;) Wir sind ja alle auf der selben Seite*.

* Außer natürlich ihr seit jetzt fiese Appleianer oder Android-Fanboys, denn das einzige richtige OS is natürlich Firefox OS. SCNR
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Trubinial Guru
User
Beiträge: 117
Registriert: Dienstag 7. April 2009, 13:40

Na da hab ich ja was ins rollen gebracht. Vielen Dank schonmal für die Antworten und entschuldigt für die späte antwort. Wie ihr schon richtig erkannt habt, habe ich noch nicht soo viel Erfahrungen mit Datenbanken.
Ich habe jetzt für jeden Thread eigene Verbindungen aufgemacht, aber brauche wohl noch bis morgen um das weiter zu testen.

Meine Datenbank ist jetzt nicht übermäßig groß, aber in den verschiedenen Threads gibt es eben doch nicht nur Lesezugriffe, sondern teilweise auch Schreibzugriffe. SQLite sollte meinen Ansprüchen auf jeden Fall genügen.

Wie wichtig ist es eigentlich die Verbindungen wieder zu schließen? Weil ich bei mir Probleme habe diese zu schließen, da ich sie nicht ausversehen zu früh schließen möchte.
Antworten