Der beste Weg, auf die Datenbank in Multi-Thread-Anwendung Android zuzugreifen?

9

Hinweis: Bitte markieren Sie diese Frage nicht als Duplikat. Ich habe einige ähnliche Fragen gestellt, konnte aber keine zufriedenstellende Antwort finden.

Ich habe an einer Anwendung gearbeitet, die Sqlite-Datenbank verwendet. Wir folgen dem Singleton-Muster, das sicherstellt, dass wir in unserer Anwendung nur eine Instanz unserer Hilfsklasse erstellen können.

%Vor%

Aber manchmal stürzt die Anwendung mit SQLiteDatabaseLockedException ab. Ich verstehe, dass diese Ausnahme kommt, wenn mehr als ein Thread / Prozess versucht, auf einmal in die Datenbank zu schreiben. Selbst wenn ein Thread / Prozess versucht, die Datenbank zu lesen, während die Schreiboperation noch läuft, wird diese Ausnahme ausgelöst.

Ich habe viel darüber gelesen und die Möglichkeiten, dies zu verhindern. In vielen Beiträgen wird vorgeschlagen, ContentProvider zu verwenden, statt die Klasse SqliteOpenHelper direkt zu erweitern und Operationen für das Datenbankobjekt auszuführen. Beim Lesen eines der Beiträge erwähnte dieser Beitrag , dass bei der Verwendung von Content Provider Sie müssen sich nicht manuell um die Umgebung mit mehreren Threads kümmern.

  

Obwohl der ContentProvider keine Thread-Sicherheit bietet, werden Sie oft feststellen, dass keine weiteren Maßnahmen erforderlich sind, um mögliche Race-Bedingungen zu vermeiden. Das kanonische Beispiel ist, wenn Ihr ContentProvider von einer SQLiteDatabase unterstützt wird; Wenn zwei Threads gleichzeitig versuchen, in die Datenbank zu schreiben, sperrt sich die SQLiteDatabase selbst, so dass sichergestellt ist, dass einer wartet, bis der andere abgeschlossen ist. Jeder Thread erhält einen gegenseitig exklusiven Zugriff auf die Datenquelle, um sicherzustellen, dass die Thread-Sicherheit erfüllt ist.

Das obige Zitat scheint etwas verwirrend zu sein, da es zuerst erwähnt, dass ContentProvider Thread-Sicherheit nicht unterstützt. Aber er kommt zu dem Schluss, dass der Anwendungsentwickler nichts unternehmen muss, um Parallelität zu erreichen.

Wenn ich SqliteOpenHelper verwende, was wird dann der beste Weg sein, um diese Abstürze zu verhindern? Ich habe darüber nachgedacht, Sperren für jede db-Operation zu verwenden.

%Vor%

Aber eines meiner Teammitglieder hat mir geraten, dies nicht zu tun, weil Java-Locks teuer sind.

Nachdem ich eines der beliebtesten Projekte von Github durchgegangen bin, habe ich das gefunden Die Entwickler haben empfohlen, jede Datenbankoperation in eine Transaktion zu integrieren.

%Vor%

Ich bin mir wirklich nicht sicher, ob das die Lock-Ausnahme verhindern kann? Dies scheint eher ein leistungsorientierter Schritt zu sein.

Nachdem ich all diese Blogs und Tutorials gelesen habe, bin ich immer noch verwirrt. Meine Hauptfragen sind

  1. Welche Option ist besser, um ContentProvider zu verwenden oder zu erweitern? SqliteOpenHelper vorausgesetzt, dass meine Anwendung keine Daten mit teilt andere Anwendungen.
  2. Setzt Java-Sperren für alle Operationen der bestem Ansatz oder gibt es einen anderen Ansatz, der besser ist als das?

Update: Basierend auf der Antwort von @Mustanar scheint es, dass die SQLiteDatabase den Sperrmechanismus übernimmt. Dies bedeutet, dass die Datenbank gesperrt wird, wenn Sie eine Schreiboperation ausführen. Aber zur gleichen Zeit, wenn ein anderer Thread versucht, eine Schreiboperation auszuführen, wird dann die zweite Operation warten, bis die Sperre freigegeben wird, oder wird sie eine android.sqlite.database.SQLiteDatabaseLockedException Ausnahme auslösen?

Update 2 : Setzen Sie ein Kopfgeld auf diese Frage, denn die Antwort scheint mir immer noch nicht klar zu sein. Ich verwende nur eine Instanz der Helper-Klasse. Aber immer noch diesen Fehler zu bekommen.

PS: Danke, dass Sie sich für so eine lange Frage ausgesprochen haben.

    
thedarkpassenger 12.02.2016, 08:44
quelle

8 Antworten

4

Verwenden Sie ein Muster Singleton , um SQLiteOpenHelper zu instanziieren, sodass in der gesamten Anwendung eine Instanz von Singleton vorhanden sein sollte. Dadurch wird sichergestellt, dass keine Lecks auftreten, und Ihr Leben wird dadurch erheblich vereinfacht, da die Möglichkeit entfällt, zu vergessen, Ihre Datenbank beim Programmieren zu schließen. Es gewährleistet auch den sicheren Zugriff auf die Datenbank in der gesamten Anwendung.

Außerdem müssen Sie Ihren eigenen Sperrmechanismus nicht implementieren. SQLiteDatabase behält den Sperrmechanismus bei. Es wird also nur eine Transaktion zu einem bestimmten Zeitpunkt ausgeführt, alle anderen Transaktionen werden in Queue mit dem Ansatz Sigleton.getInstance() ausgeführt. Stellen Sie einen einzelnen Zugriffspunkt auf die Datenbank sicher.

Darüber hinaus müssen Sie bei dieser Methode Ihre Datenbankverbindungen nicht schließen, da SQLiteDatabase alle Referenzen freigibt, sobald die Transaktion abgeschlossen ist. So CustomSQLiteHelper.getInstance() sollte immer dann verwendet werden, wenn Sie CRUD-Operationen ausführen und Ihren Sperrmechanismus entfernen möchten.

Weitere Informationen, gehen Sie bitte durch den Blog Ссылка und sehen Sie die Kommentare auch.

Hoffe, das hilft.

    
Mustansar Saeed 12.02.2016, 09:13
quelle
1

Ich würde definitiv empfehlen, dass Sie Ihre Datenbank in ein ContentProvider einbinden.

Es bietet nützliche Funktionen und verschiebt viele potentiell knifflige Aufgaben in das Framework. Sie müssen sich nicht mit Singleton-Datenbankhelfern beschäftigen, da Sie garantiert eine einzige ContentResolver -Instanz für Ihre App haben, die Befehle an den gewünschten Provider weiterleitet.

Sie können das gesamte Loader -Framework und nette Hilfsklassen wie AsyncQueryHandler verwenden, um mit einigen der häufigsten Einschränkungen des DB-Zugriffs umzugehen.

Ich würde Ihnen raten, den Zugriff auf Ihre CRUD-Methoden zu synchronisieren. Wie in der Dokumentation und den vorherigen Antworten erwähnt, sollte dies von der Datenbank selbst erledigt werden. Ganz zu schweigen von der möglichen Leistungseinbuße in einer App, die eine intensive Datenbanknutzung erfordert.

Randnotiz: Als ersten Schritt kann es nützlich sein, wenn Sie versuchen herauszufinden, was diese Abstürze verursacht - sind sie sporadisch? Ein konkreteres Beispiel könnte sich bei der Fehlersuche als nützlich erweisen.

    
Danail Alexiev 21.02.2016 01:02
quelle
1

Abfrage 1 (Singleton):

Können Sie den doppelten Sperrcode ändern, um ein echtes Singleton-Objekt zu erstellen?

%Vor%

Schauen Sie sich diesen Artikel an verstehe die Probleme mit Doppelverriegelung.

Sehen Sie sich die verwandte SE-Frage an:

Was ist ein effizienter Weg ein Singleton-Muster in Java implementieren?

Ich bevorzuge es, Singleton

zu erstellen %Vor%

Andere Vorgehensweise:

%Vor%

Noch ein Ansatz, wenn Sie immer noch faul Singleton brauchen.

Sehen Sie sich @ Nathan Hughes answer in dieser SE-Frage an:

Synchronisiert und sperrt die Singleton-Factory

Abfrage 2: (Sperren)

Die Verwendung von Java Lock ist keine schlechte Idee, um die Konsistenz der Anwendung zu verbessern. Konsistenz gewinnt die Kosten.

Von der Seite SQLiteDatabaseLockedException ,

  

Wird ausgelöst, wenn das Datenbankmodul die Datenbanksperren nicht abrufen konnte, um seine Aufgabe zu erledigen. Wenn die Anweisung ein [COMMIT] ist oder außerhalb einer expliziten Transaktion auftritt, können Sie die Anweisung erneut versuchen. Wenn die Anweisung kein [COMMIT] ist und innerhalb einer expliziten Transaktion auftritt, sollten Sie die Transaktion zurücksetzen, bevor Sie fortfahren.

Ich hoffe, Sie sollten die obige Empfehlung mit dem Code-Snippet befolgen, das Sie in Ihrer Frage (Github) -Projekt veröffentlicht haben. Ich mag diese Idee und obige Empfehlung ist in Übereinstimmung mit Github Beispielcode.

    
Ravindra babu 20.02.2016 12:22
quelle
0

Sie können die Inhaltsanbieter verwenden.

    
Ahmad Aghazadeh 21.02.2016 12:20
quelle
0

Ich denke, in den meisten Fällen reicht die Verwendung einer Lese- / Schreibsperre aus. Siehe diese Antwort . Die Lösung verwendet nicht ContentProvider . Stattdessen verwendet es ReentrantReadWriteLock class in java.util.concurrent.locks package.

    
Takahiko Kawasaki 21.02.2016 12:37
quelle
0
  
  1. Welche Option ist besser, ContentProvider zu verwenden oder SqliteOpenHelper zu erweitern, da meine Anwendung keine Daten mit anderen Anwendungen teilt.
  2.   

Ich würde sagen, dass das Erweitern von SqliteOpenHelper die bessere Wahl ist, wenn Sie keine Daten teilen. Das liegt hauptsächlich an der Dev-freundlichen API als ContentProvider :-) Und es gibt noch etwas, das in offizielle Dokumentation :

  

Entscheiden Sie, ob Sie einen Inhaltsanbieter benötigen. Sie müssen einen Inhaltsanbieter erstellen, wenn Sie eine oder mehrere der folgenden Funktionen bereitstellen möchten:

     
  • Sie möchten komplexe Daten oder Dateien anderen Anwendungen anbieten.
  •   
  • Sie möchten Benutzern erlauben, komplexe Daten aus Ihrer App in andere Apps zu kopieren.
  •   
  • Sie möchten benutzerdefinierte Suchvorschläge mithilfe des Suchframeworks bereitstellen.
  •   

Also in Ihrem Fall - es ist ziemlich überflüssig

Für Singleton-Initialisierung - manchmal brauchen Sie nicht, dass es initialisiert wird:

%Vor%

Aber Sie müssen den App-Kontext mit Ihrer Anwendungsklasse offen legen:

%Vor%

plus Konfiguration in Manifest. Auf diese Weise vermeiden Sie die Synchronisierung während der Erstellung.

Wenn Sie wirklich brauchen, dass dieser Singleton faul initialisiert wird - dann kann ich sagen, dass doppelte Sperrung (DCL) sehr schlecht ist. Für eine sichere verzögerte Initialisierung sollten Sie das folgende Muster verwenden:

%Vor%
  
  1. Ist das Setzen von Java-Sperren für alle Operationen der beste Ansatz oder gibt es einen anderen Ansatz, der besser ist?   Dies ermöglicht eine sichere verzögerte Initialisierung, ohne etwas zu sperren.
  2.   

Es ist nicht notwendig, eine Sperre zu machen (solange Ihr DAO nicht statusfrei ist). Überprüfen Sie die Dokumentation für SQLiteDatabase

    
Gaskoin 21.02.2016 12:55
quelle
-1

Um Sperrfehler zu vermeiden, legen Sie ein Zeitlimit für belegte Anrufe von etwa zwanzig Sekunden fest, alle Verbindung. Dann erhalten Sie nur dann eine Ausnahme, wenn Ihre App länger als eine Transaktion läuft.

Sie könnten auch ein einzelnes SQLiteDatabase -Objekt von allen Threads verwenden, aber dann müssten Sie es manuell für alle Transaktionen sperren.

    
CL. 12.02.2016 12:56
quelle
-2

Wie in den Kommentaren besprochen, gibt es keine Blockierungssperre, wenn Sie eine DB-Instanz verwenden. Also ich zweifle an DeadLock, weil Sie eine Transaktion verwenden.

Eine Art DeadLock-Beispiel ist:

  1. in einer Transaktion:

    update Eine Tabelle - & gt; B-Tabelle aktualisieren - & gt; commit

  2. in einer anderen Transaktion:

    update B Tabelle - & gt; update Eine Tabelle - & gt; commit

so in der Mitte, 1 wartet 2 und 2 wartet 1 zur gleichen Zeit, so Deadlock passiert.

Wenn es wirklich Deadlock ist, können Sie die Abfrage reparieren, um dies nicht zuzulassen oder Sie können die Queued-Abfrage (oder asynchrone Abfrage) wie Actor verwenden.

Zum Beispiel unterstützt die Bibliothek commons-dbutils einen AsyncQueryRunner, dem Sie einen ExecutorService bereitstellen, und gibt einen Future zurück. (aus: Ist asynchroner jdbc-Aufruf möglich? )

    
Gary Y Kim 12.02.2016 10:14
quelle