SQLite 3 Daten gleichzeitig lesen und schreiben

  • Hallo,

    Im Moment arbeite ich an einem kleinen Projekt, bei dem Daten vom Raspberry (Raspbian|Jessie Lite), über ein kleines C Programm, das in einer Dauerschleife läuft, in eine SQLite Datenbank geschrieben werden.
    Ausgelesen werden die Daten über den Browser mit PHP.
    Es funktioniert zwar alles, aber nur weil ich in beiden Programmen einen großen delay drin hab damit sich die zwei nich in die Quere kommen.
    Denn wenn einer der beiden auf die Datenbank zugreifen will während der andere schreibt oder liest bekomme ich immer beim C Programm die Fehlermeldung:"SQL error: database is locked" und bei PHP einen Internal Server Error.

    Zu meine Frage.
    Gibt es eine Möglichkeit das beide gleichzeitig lesen und schreiben?
    Denn es dürfte ja kein Problem darstellen denn das C Programm verwendet die Datenbank nur schreiben und der Browser lesend.

  • Hi,


    Zu meine Frage.
    Gibt es eine Möglichkeit das beide gleichzeitig lesen und schreiben?


    Das ist gegen das Konzept einer vernünftigen Datenhaltung ... das Ergebis wären "dirty reads" ... (mit anderen Worten: das Lesen würde keinen Sinn machen, weil nicht gewährleistet ist, dass der gelesene Wert auch stimmt).
    Verringere die Zeit, die Schreibsperren aktiv sind und mache eine kurze Pause und einen Wiederholversuch bei einem Lesefehler.
    Das sollte bei einer Browser-Anwendung keine Rolle spielen ...

    cu,
    -ds-

  • Versuche mal, deine Schreiboperation per Transaction zu kapseln: https://www.sqlite.org/lang_transaction.html

    Ansonsten verwende eine (richtige) DB, welche den "simultanen" Zugriff besser im Griff hat, also z.B. MySQL, MariaSQL, Oracle, PostgreSQL usw...

    Gleichzeitig schreiben und lesen geht (in der präzisen Auslegung des Wortes) sowieso nicht. Die Operationen müssen immer serialisiert werden... Transaktions gestatten jedoch, dass die Schreiboperation "transparent" im Hintergrund laufen kann... in wie weit SQlite (kommt von "leicht", also nicht so schwergewichtig wie eine richtige DB) dieses Transaktionsprinzip wirksam umgesetzt hat, musst du selbst testen (und dann bitte hier berichten! ).

    cu, das Zen

  • Servus,


    Gibt es eine Alternative Datenbank die anstatt einem "dirty read" den alten Wert wiedergibt wenn während einem Schreibvorgang gelesen wird?

    So was gibt es imho nicht und das wäre auch weder seriös noch zielführend.

    ZEN: da bist Du imho auf dem Holzweg ... während des Schreibens die alten Daten zurückliefern ... das wäre das k.o. Kriterium schlechthin ...

    Wieso willst Du denn unbedingt einen definitiv falschen Wert anzeigen?
    Mach', wie gesagt, zwei- oder drei- Leseversuche (dann sollte das Schreiben locker erledigt sein) und/oder zeig' stattdessen n/a oder ?? an, dann weiss der Anwender wenigstens, dass der Wert im Moment nicht verfügbar ist.

    Alles andere ist in meinen Augen vollkommener Unsinn ...

    btw: transaktionen haben damit erst mal gar nichts zu tun. Die dienen lediglich dazu verschiedene Operationen zusammenzufassen und zu gewährleisten, dass die eintweder komplett alle oder gar nicht ausgeführt werden. Das macht man um die Konsistenz der Daten zu gewährleisten ... Sperren werden davon vollkommen unabhängig gesetzt ...
    Deshalb soll man ja auch Transaktionen möglichst kurz halten, weil sonst beim commit die Locks unnötig lange gehalten werden (eben bis zum Ende der Transaktion und nicht nur bis zum Ende der Einzel-Operation).


    cu,
    -ds-

  • Also ...


    ... die Verzögerung wäre ja nur minimal, da nach Abschluss des Schreibvorganges,bei wiederholtem lesen, der richtige Wert ausgelesen werden würde oder?

    eben ... dann lies ihn halt bei einem Zugriffsfehler gleich noch mal und dann passt der i.d.R. auch und Du bekommst keinen Fehler.
    Ich weiß nicht von welcher "Zeitspanne" Du glaubst, dass wir hier reden ...


    ... Lediglich für die Dauer des Schreibens würde sich dann der betreffende Prozess einen exklusiven Zugang sichern.

    das ist ja genau seine Frage ... er hätte halt lieber schmutzige Daten ...

    cu,
    -ds-


  • ZEN: da bist Du imho auf dem Holzweg ... während des Schreibens die alten Daten zurückliefern ... das wäre das k.o. Kriterium schlechthin ...
    Wieso willst Du denn unbedingt einen definitiv falschen Wert anzeigen?

    Muss ich dich jetzt aber (leider) enttäuschen:

    Während innerhalb einer Transaktion Daten in eine (oder mehrere Tabellen) geschrieben werden (neuer Datensatz angelegt oder Datensatz wird gelöscht), geschieht das in einem Schattenbereich der Tabelle.
    Leseoperationen erfolgen nach wie vor aus dem aktuellen Bereich der Tabelle, da die aktuell geschriebenen Daten ja noch nicht "commited" sind.

    Erst wenn der "commit" - Befehl ausgeführt wird, erfolgt das "umzeigern", ab diesem Moment ist die Tabelle wieder aktuell.
    Wenn der commit - befehl nicht kommt, passiert gar nix (oder wenn ein "rollback" - Befehl erscheint, werden lediglich die neu erzeugten Datenstrukturen entfernt und gut ist).

    Diese Technologie, ein blockieren von Leseoperationen bei "gleichzeitigen" Schreiboperationen zu verhindern, ist bei Oracle z.B. schon seit Jahren drin und wird immer feiner ausgebaut (Table look - Problematik beim Schreiben des gleichen Datensatzes)...

    Kannst man selbst recht einfach testen (MySQL):
    Du schreibst Daten rein und liest die zyclisch aus...
    Du schreibst mit einem anderen Programm neue Daten rein... ("Begin transaction" ist bei MySQL 'eh der default) : Solange du kein 'commit' abgesetzt hast sind die neuen Daten nicht sichtbar (es sei denn, "auto-commit" ist aktiviert - in diesem Fall wird nach dem insert - Befehl das commit selbst angehängt, ist bei MySQL meist der default (konfigurierbar), bei PostgreSQL/Oracle z.B. nicht...

    Wenn der gleiche Datensatz in der Tabelle überschrieben werden soll, gibt es noch ein paar Besonderheiten (diverse 'look'- Einstellungen bei den verschiedenen DBs... das führt jetzt zu weit...) ist aber, falls ich den TO richtig verstanden habe, 'eh nicht gefordert...

    Zu guter Letzt gibt es noch das "Snapshot"-Konzept:
    Da wird der Zustand einer Tabelle "eingefroren" um einen genau definierten Zustand der Tabelle(n) zu präsentieren - auch das macht Sinn, da es Anwendungen gibt (z.B. Auswertungen über mehrere Tabellen), welche mit "variablen" Dateninhalten ungenaue Ergebnisse bringen würden...


  • das ist ja genau seine Frage ... er hätte halt lieber schmutzige Daten ...

    Ich rate jetzt mal: ProgrammierFormelSepp will eigentlich nicht schmutzige Daten, sondern keine Fehlermeldungen. Dafür wäre er dann auch bereit schmutzige Daten in Kauf zu nehmen. Das gibt SQLite wohl nicht her. Was es aber bietet, ist das Setzen eines Timeouts: https://www.sqlite.org/c3ref/busy_timeout.html, http://php.net/manual/de/sqlite3.busytimeout.php. Wenn keiner gesetzt ist, gibt der Versuch, eine exklusiv geöffnete Datenbank zu öffnen, wohl in der Tat unmittelbar einen Fehler zurück. Wenn einer gesetzt ist, wartet SQLite maximal so lange, um doch noch Zugriff zu bekommen. Das könnte doch schon die Lösung sein.


  • ...
    Erst wenn der "commit" - Befehl ausgeführt wird, erfolgt das "umzeigern", ...
    ...


    ... und das im Grunde seine "Schreib-Operation". Solange der commit nicht durch ist bekommst Du beim Lesen der betroffenen Daten auch einen Fehler und nicht die alten Inhalte.
    Wenn das nicht funktionieren würde, hätten wir seinerzeit im Hochregal keine einzige Palette mehr gefunden, die KPs wäre übergelaufen oder leer gewesen, die Bestände hätten nicht gestimmt, usw. usw. ...

    Ist also Augenwischerei zu sagen, mach die Schreib-Operation in einer Transaktion ... irgendwann wird gesperrt und geschrieben ( wie Du es ja auch weiter oben bereits gesagt hattest ) ... und deshalb bekommt er irgendwann auch seinen Fehler ...


    Ich rate jetzt mal: ProgrammierFormelSepp will eigentlich nicht schmutzige Daten, sondern keine Fehlermeldungen. Dafür wäre er dann auch bereit schmutzige Daten in Kauf zu nehmen. ...

    Da hast Du wohl recht ... geht aber nicht so einfach ... er könnte natürlich die Daten vor dem Einfügen in SQLite irgendwo im tempfs zwischenspeichern und von da lesen ...
    Oder, wie gesagt, einfach noch ein zweites- oder drittes mal lesen ...

    cu,
    -ds-


  • ... und das im Grunde seine "Schreib-Operation". Solange der commit nicht durch ist bekommst Du beim Lesen der betroffenen Daten auch einen Fehler und nicht die alten Inhalte.
    Wenn das nicht funktionieren würde, hätten wir seinerzeit im Hochregal keine einzige Palette mehr gefunden, die KPs wäre übergelaufen oder leer gewesen, die Bestände hätten nicht gestimmt, usw. usw. ..
    Ist also Augenwischerei zu sagen, mach die Schreib-Operation in einer Transaktion ... irgendwann wird gesperrt und geschrieben ( wie Du es ja auch weiter oben bereits gesagt hattest ) ... und deshalb bekommt er irgendwann auch seinen Fehler ...

    Der Witz ist doch: Der commit-Befehl ist sehr schnell, quasi "atomar" (im Hintergund laufen noch diverse Indexing-Operations, die auch recht schnell sind (Hash/Btree)...
    Im Idealfall ist der Sperrzustand nach wenigen ms vorbei - solange werden select-Requests nicht zugelassen, quasi "gequeued" - da blockiert nix wirklich...

    Das Problem, was er hat, ist SQlite als rein filebasierte DB: Jede Operation will singleton das DB-File anfassen: Für Programme, wo man der einzige User ist, ist das kein Problem.
    Für DBs, wo mehrere Programme asynchron lesend+schreibend zugreifen, ist SQlite nur begrenzt sinnvoll (Einbauen von blocking-Maßnahmen, wie schon beschrieben wurde).

    Ich habe hier auf meinem NAS eine Art Notizbuch-Programm laufen: Das speichert die "Notizen" in einer SQLite-DB mit vielen Tabellen.
    Multiuser-Zugriff (per Web-Interface) ist möglich, die Notizbuchapp kümmert sich um die Serialisierung.
    Das Ergebnis ist ein manchmal etwas zäher Zugriff auf die Seiten, wenn jemand anders gerade eine Webseite abspeichert...

    Das alles kann man sich mit einer "richtigen" DB sparen...

  • Also m.E. steht das in keinem Verhältnis, deshalb jetzt Oracle oder was weiss ich einzusetzen.

    Ich kenne SQLite zu wenig ... da sollte es doch, genauso wie seinerzeit bei C-ISAM, möglich sein, einen kurzen Lock für einen Schreibztugriff zu setzen der anschliessend gleich wieder aufgehoben ist. Das hat vor 40 Jahren schon funktioniert ... warum sollte es heute nicht mehr gehen. Möglicherweise ist das sogar default bei SQLite.

    Das Problem des TO sehe ich eher darin, dass er überhaupt kein Fehlerhandling macht und keins machen will.
    Es gibt halt nun mal Situationen, mit denen muss man immer rechnen, und die werden bei mySQL nicht weniger ... eher im Gegenteil ...

    cu,
    -ds-


  • Da hast Du wohl recht ... geht aber nicht so einfach ... er könnte natürlich die Daten vor dem Einfügen in SQLite irgendwo im tempfs zwischenspeichern und von da lesen ...
    Oder, wie gesagt, einfach noch ein zweites- oder drittes mal lesen ...

    Warum denn so umständlich. Es sollte doch, wie oben geschrieben, reichen, einen Timeout zu setzen. Dann kriegt er keinen Fehler, sondern die lesende Applikation wartet eben die vermutlich


    wenigen ms

    bis der Schreibprozess vorbei ist, und liest die Daten erst dann.


    Das Problem, was er hat, ist SQlite als rein filebasierte DB: Jede Operation will singleton das DB-File anfassen: Für Programme, wo man der einzige User ist, ist das kein Problem.
    Für DBs, wo mehrere Programme asynchron lesend+schreibend zugreifen, ist SQlite nur begrenzt sinnvoll (Einbauen von blocking-Maßnahmen, wie schon beschrieben wurde).

    Wie gesagt: Die Blockingmaßnahmen muß man nicht einbauen, die sind schon drin. Man muß sich nur entscheiden, wie man mit einem lock umgehen will: Abwarten oder Fehler werfen/behandeln. Klar ist eine "richtige" Datenbank für massiv konkurrierende (insbesondere Schreib-)Zugriffe besser gerüstet, aber wir reden hier von gerade mal zwei Applikationen, von denen nur eine schreiben will. Das bekommt auch SQLite locker hin.

  • MySQL ist per default überall dabei... bei rasbian bin ich nicht ganz sicher, gehört aber zu den Paketquellen... ist simpel nachinstallierbar.

    Der Syntax (sql) ist in dem Bereich, den die meisten User nutzen, identisch.

    Es gibt keinen Grund, für Messdaten SQlite einzusetzen, außer man weiß es nicht besser... weil "SQlite eben da ist" / "der Beispielcode im Internet (der Einfachheit halber) SQlite verwendet hat" / "man sich gar keine Gedanken gemacht hat"....

    SQlite verwende ich maximal für Konfigurationsdaten... aber jeder, wie er will ---
    Automatisch zusammengefügt:


    Wie gesagt: Die Blockingmaßnahmen muß man nicht einbauen, die sind schon drin. Man muß sich nur entscheiden, wie man mit einem lock umgehen will: Abwarten oder Fehler werfen/behandeln. Klar ist eine "richtige" Datenbank für massiv konkurrierende (insbesondere Schreib-)Zugriffe besser gerüstet, aber wir reden hier von gerade mal zwei Applikationen, von denen nur eine schreiben will. Das bekommt auch SQLite locker hin.

    Bei DBs != SQLite braucht man sich auf dem Level, wie hier die DB verwendet werden soll überhaupt keine Gedanken zu machen bzgl. Blockings oder Locks.

    Bei mir laufen seit ca. 2-3 Jahren mehrer Messdatenbanken (auf Raspi & Cubitruck) mit inzwischen ca. +900.000DS in den Haupttabellen, die werden periodisch beschrieben und aperiodisch gelesen, teilweise mit "schweren" select-Statements - ohne Probleme - auch mit Spiegel auf eine MySQL-DB in Internet als "backup"...

    Es ist doch so:
    Man fängt klein an "mal ein paar Daten" - plötzlich werden es mehr und mehr und neue Anforderungen kommen dazu.
    Wenn man den Moment verpasst hat, die besseren Tools/Werkzeuge (die DB ist nur ein Tool/Werkzeug) zu wählen, kommt man an einen Punkt, wo die Probleme zu groß werden... und der Spaß verschwindet, weil man sich mit den Problemen beschäftigen muss und nicht mit dem, was man eigentlich machen wollte.

    Aber ok, ich bin dann raus...

  • SQLite ist per default überall dabei...

    Die Syntax (sql) ist in dem Bereich, den die meisten User nutzen, identisch.

    Es gibt keinen Grund, für Messdaten MySQL einzusetzen, außer man weiß es nicht besser... weil "MySQL eben da ist" / "der Beispielcode im Internet (der Einfachheit halber) MySQL verwendet hat" / "man sich gar keine Gedanken gemacht hat"....

    :D

    Im Ernst: Solange wir nicht wissen, welche Daten in welcher Frequenz der TE erhebt und verarbeitet, denke ich nicht, daß irgendjemand sagen kann, welches Tool für seinen Job am besten geeignet ist. Persönlich sehe ich im Moment keinen Hinweis, daß SQLite nicht ausreichen würde - der konkurrierende Zugriff durch zwei Programme ist auf jeden Fall kein Grund dafür. Aber natürlich spricht auch nichts gegen MySQL (oder Oracle, wenn er zu viel Geld hat...). M.E. letztlich Geschmackssache.

  • Danke für die vielen Antworten.
    Ich sehe jetzt das das Problem wirklich ist das ich mich mit meinen Fehlern nicht beschäftige (ich warte immer den gleichen delay ab egal ob es funktioniert hat oder nicht).
    Wie komme ich am besten zu den Fehlermeldungen?

    $db = new SQLite3("master.db");
    $res = $db->query("select * from werte");

    steht bei einem Fehler in $res der Fehler drin?

  • Boah eh ... soll ich jetzt für Dich die SQLite Doku lesen ;) ???
    Nein danke ...
    ja, wird - zumindest aus dem Bauch - wohl so oder so ähnlich sein.
    Zumindest steht in $res eine Art Fehler-Indikator ...

    Wobe: ein "select *" ist immer eine schlechte Idee ... allein schon durch einen gezielten select kannst Du u.U. Dein Schreibsperren-Problem umgehen. "Select *" sperrt temporär vermutlich die ganze Tabelle (gegen Schreibzugriffe) ... ein select auf einen bestimmten Datensatz nur eben diesen Datensatz ... wobei der "select *" natürlich umso länger dauert, je größer die Tabelle wird. Da sind Kollisionen schon vorprogrammiert.

    Na ... wühl' Dich mal durch die Doku und dann schauen wir uns gemeinsam mal das Ergebnis Deiner Bemühungen hier an und optimieren das entsprechend ... vorausgesetzt Du willst das ...

    cu,
    -ds-

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!