SQLite Zugriffsproblem

  • Antworten:10
Michael H
  • Forum-Beiträge: 127

10.09.2012, 22:15:50 via Website

Hallo zusammen

Ich habe einen Thread in meiner App welche im Hintergrund GPS Koordinaten einsammelt und diese in einer SQLite Datenbank abspeichert. Ein anderer Thread holt sich die vorhandenen GPS Koordinaten aus der Datenbank und sendet diese über eine Socketverbindung an einen Server. Nach erfolgreichem senden der Koordinaten werden diese vom (senden)Thread aus der DB gelöscht.
Desweiteren möchte ich nun Nachrichten zwischen Client und Server austauschen, welche ebenfalls über die Socketverbindung laufen werden.

Dazu will ich die vom Server empfangenen Nachrichten in einer ListView direkt aus der Datenbank anzeigen. Mit einem Klick soll man dann die jeweilige Nachricht in einem Detailview lesen können.

Das Know-How über Socketverbindungen ist bereits vorhanden. Über SQLite Datenbanken habe ich bereits viel Recherchiert. jedoch ist meine aktuelle Lösung nicht optimal. Ich schreibe immer über dasselbe DB-Objekt welches am Beginn der Applikation erstellt- und am Ende der App zerstört wird. Ich hatte in Testversuchen auch schon das Problem dass ich nicht zwei Cursor gleichzeitig öffnen konnte... Da ich in verschiedenen Threads in die DB schreibe, muss ich das irgendwie Managen können. Da nun die ListView mit einem SimpleCursorAdapter arbeitet um die Daten aus der DB zu holen muss ich ja mein Cursor-Objekt so lange die Activity angezeigt wird, geöffnet halten. Was ist wenn in dieser Zeit ein anderer Thread etwas in die DB schreiben will?

Nun weiss ich nicht so ganz wie ich das ganze bezüglich SQLite Datenbank angehen soll. Ich erwarte keine fix fertige Lösung. jedoch erhoffe ich mir hiermit den einen oder anderen sinnvollen Tipp von Euch...

Gruss Michi

Antworten
Michael H
  • Forum-Beiträge: 127

11.09.2012, 15:46:56 via Website

Danke für die Antwort.
Hmmm... Ich habe mir schon überlegt ob das ganze mit synchronized Methoden funktioniert. So dass man einen zentralen Ansprechpunkt der DB hat, welcher mit synchronized implementiert wurde. So gäbe es doch schlussendlich nur immer einen Zugriff auf die Datenbank und die weiteren müssten aussen anstehen...?

— geändert am 11.09.2012, 15:47:54

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

11.09.2012, 16:13:46 via Website

Das was Du beschrieben hast wird so nicht ganz funktionieren. Ich war gestern nur mit meinem kleinen Handy unterwegs deshalb war die Antwort relativ kurz.

* Einfügen der Koordinaten: Kein Problem. Die werden von Dir, hoffentlich mittels PreparedStatements, gegen die DB gefeuert damit es sehr schnell geht. Sie nehmen sich einen exklusiven Lock und hängen die Daten rein (Index, Daten). Kann mit allem parallel laufen.

* Löschen von Koordinaten: Dto. wird halt nur gelöscht.

* Lesen von Koordinaten: Nur ein kleines Problem. Im Standard Isolation Level werden nur Daten gelesen die commitet sind. Das würde ich auch so lassen. Allerdings würde ich in Chunks (zur Übertragung und Löschung) lesen und diesen Cursor immer schließen. SQLite hat meines Wissens keine "WHERE CURRENT OF--cursor-name" Klausel - was auch mit einer zwischengeschalteten Netzwerk-Kommunikation sehr unglücklich wäre. Deshalb würde ich mir immer einen Block holen, einzeln übers Netz schicken und Zug um Zug löschen. Danach Cursor wieder auf etc. EDIT: Oder requery() ausführen damit der Cursor die neuen Daten mitbekommt.

* Eintragen neuer Nachrichten: Kein Problem, siehe Einfügen der Koordinaten

* Lesen der Nachrichten: Hier kommt ein etwas größeres Problem - ist aber machbar. Ein Cursor ist ja ein Resultset - eine Sicht auf die Daten zu einem bestimmten Zeitpunkt. Nun fließen aber parallel weitere Nachrichten rein. Der Standard CursorAdapter bekommt das nicht mit - Du musst bei diesem immer CursorAdapter.notifyDataSetChanged() oder Cursor.requery() ausführen. Damit veranlasst Du eine Aktualisierung. Neuere CursorLoader bringen von Hause aus Observer mit. Damit müsste es gehen. Leider gibt es die erst ab API11 sie sind aber Bestandteil der Support-Library.

* Zentrale Instanz: Ich arbeite ausschließlich so. Das ist auch ausdrücklich "nicht untersagt" ;-) In den Code Snippets zwei Bretter weiter findest Du dazu von mir einen Beitrag. Ich nutze immer eine Subclass von Application. Ob das da drin statisch sein muss "MyApplication.getSqliteOpenHelper.deleteIrgendwas(1)" oder ob Du Singletons nimmst ist Dir überlassen. Parallele Aktivitäten sind kein Problem für Datenbanksysteme. Wenn die das nicht können dann haben die ihre Daseinsberechtigung verloren.

— geändert am 11.09.2012, 16:15:46

Antworten
Michael H
  • Forum-Beiträge: 127

11.09.2012, 16:48:38 via Website

Danke für die Ausführliche Antwort, Harald!

Was meinst du beim * Einfügen mit "Kann mit allem parallel laufen"? Wenn ich die DB an einem anderen Ort geöffnet habe, was passiert dann? Stören sich die beiden Instanzen nicht gegenseitig?

Wenn ich einen Cursor offen habe und Daten aus der DB in einer ListView anzeige, habe ich ja eigentlich eine permanente Verbindung zur Datenbank. Was ist jetzt wenn ich die Datenbank nochmals schreibend öffne und einen Cursor erzeuge, funktioniert das? Oder in anderen Worten: Kann ich zwei Cursor gleichzeitig offen halten?

Ich habe gelesen dass getWritableDatabase() und getReadableDatabase() schlussendlich beide schreibend die Datenbank öffnen, kannst du da zustimmen?

— geändert am 11.09.2012, 16:49:14

Antworten
Michael H
  • Forum-Beiträge: 127

12.09.2012, 09:21:01 via Website

Guten Morgen

Habe mit dem ganzen mal ein bisschen rumgespielt. Habe dazu 2 Threads gestartet. Wobei der eine alle 10 Millisekunden etwas reinschreibt und der andere alle 30 Millisekunden alle DB einträge durchrattert und löscht. Nebenbei zeige ich auf dem Screen noch eine Liste mit allen Einträgen der DB an, welche einen Aktualisierungsknopf beinhaltet um die Liste zu aktualisieren.

Das oben beschriebene funktioniert ziemlich gut! Nur zwischendrinn bekomme ich ab und zu folgende Exception:

SQLiteException: database is locked

Nun frage ich mich wieso dieser Fehler nicht häufiger auftritt, denn der Cursor der ListView ist ja immer offen und die anderen beiden Threads versuchen immer zu inserten / deleten? Da müsste doch die database "fast" immer "locked" sein, nicht? Wann wird da unterschieden?

Gruss Michi

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

12.09.2012, 10:16:16 via Website

10ms? Was soll das? Bekommst Du alle 10ms einen Koordinaten-Update?

Denk nicht drüber nach. Konzentriere Dich auf Deine App und wie Du das Szenario sauber implementierst. Dann hast Du keine Probleme mit der Datenbank.

Ein Lock in dem Sinne wie Du den vermutlich interpretierst gibt es nicht. Datenbanken locken z.B. Rows bzw. Tabellen für exklusive Zugriffe wie z.B. einen INSERT. Wenn nun parallel ein weiterer exklusiver Zugriff auf diese Row erfolgt (z.B. ein DELETE) dann entsteht ein Deadlock. Aber selbst diese sind auf Deiner Ebene unproblematisch da Datenbanksysteme dafür eine Lock-Eskalations-Strategie nutzen. Einem der beiden Zugriffe wird dann Vorrang gegeben und der andere läuft dann in den Rollback. Wenn Du also während des INSERTs direkt den DELETE auf die selbe Row machst dann bekommst Du exakt diese Situation. Auch in extrem kurzen SELECT Situationen im Shared-Zugriff kann das passieren. Das ist bei Deinem völlig irren 10ms Test nicht auszuschließen. Um das zu umgehen musst Du halt auf Dirty-Read (Uncommited Read) umstellen.

Das sind DBA Dinge. Wenn Du das für eine App benötigst dann wird die App das System in die Knie zwingen und somit das Device überbeanspruchen.

Antworten
Michael H
  • Forum-Beiträge: 127

12.09.2012, 10:49:53 via Website

Harald Wilhelm
10ms? Was soll das?
:) Natürlich nicht... ich habs wohl mit meinen Tests wiedermal übertrieben... :)

Jetzt ist mir auch klar wieso der Cursor der ListView nicht die Datenbank sperrt... Da dieser ja die Records nicht exklusiv Sperrt, hat dies auf die anderen Threads auch keinen Einfluss. Habe ich das richtig verstanden?

Nochmals vielen Dank, Harald! Was würde ich nur ohne deine Aufklärungen machen.. :) Super so Leute im Forum anzutreffen, vielen Dank!

Antworten
Gelöschter Account
  • Forum-Beiträge: 694

12.09.2012, 11:35:25 via Website

Michael H
Habe ich das richtig verstanden?

Nicht ganz.

Der Fetch, also der Cursor, ist ein Blick auf die Daten zu einem Zeitpunkt X. Je nach Isolation Level "sieht" dieser Cursor unter Umständen unterschiedliche Daten (commited, uncommited).

Das Lesen des Cursors erzwingt einen Shared Lock - und zwar mit jeder Row die Du "besuchst". Parallele exklusive Zugriffe auf andere Tabellen funktionieren nur in den Pausen. Diese exklusiven Zugriffe werden aber erst commited wenn der Cursor geschlossen ist. Insofern ist der Cursor ein kritischer Punkt - auch wenn er nur liest. Das meinte ich in einem der Posts zuvor als ich anregte das Du den Cursor in Chunks liest und wieder schließt. Diese Locking-Strategien von SQLite kannst Du hier nachlesen. Sie unterschieden sich teilweise gravierend von anderen Datenbanksystemen.

Hier merkt man eine der vielen Schwächen von SQLite. Man kann aber uch nicht erwarten das eine 300KB SQLite-Engine das selbe leistet wie z.B. eine IBM DB2 Engine.

— geändert am 12.09.2012, 11:40:37

Antworten
Michael H
  • Forum-Beiträge: 127

12.09.2012, 13:57:15 via Website

Jetzt muss ich trotzdem nochmal nachfragen:

Wenn ich von der Datenbank einen Cursor erhalte und diesen auf die Liste anwende, sind dann alle Datensätze welche der Cursor enthält solange gesperrt bis der Cursor geschlossen wird? Oder nur wenn dieser Lese / Schreibzugriffe auf die einzelnen records macht?
Entschuldige für die blöde frage, aber ich konnte nirgends eine Antwort darauf finden.

Antworten