web-dev-qa-db-ja.com

Androidスレッドとデータベースのロック

AsyncTasksを使用して、データベーステーブルとカーソルにアクセスしています。

残念ながら、データベースのロックに関する例外がときどき見られます。

E/SQLiteOpenHelper(15963): Couldn't open iviewnews.db for writing (will try read-only):
E/SQLiteOpenHelper(15963): Android.database.sqlite.SQLiteException: database is locked
E/SQLiteOpenHelper(15963):  at     Android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method)
E/SQLiteOpenHelper(15963):  at     Android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.Java:1637)
E/SQLiteOpenHelper(15963):  at     Android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.Java:1587)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.Java:638)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.Java:659)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.Java:652)
E/SQLiteOpenHelper(15963):  at Android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.Java:482)
E/SQLiteOpenHelper(15963):  at Android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.Java:193)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.Java:98)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.Java:158)
E/SQLiteOpenHelper(15963):  at com.iview.Android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.Java:73)
E/SQLiteOpenHelper(15963):  at com.iview.Android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.Java:121)
E/SQLiteOpenHelper(15963):  at com.iview.Android.async.GetNewsTask.doInBackground(GetNewsTask.Java:338)
E/SQLiteOpenHelper(15963):  at com.iview.Android.async.GetNewsTask.doInBackground(GetNewsTask.Java:1)
E/SQLiteOpenHelper(15963):  at Android.os.AsyncTask$2.call(AsyncTask.Java:185)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.Java:256)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.FutureTask.run(FutureTask.Java:122)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.Java:648)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:673)
E/SQLiteOpenHelper(15963):  at Java.lang.Thread.run(Thread.Java:1060)

読み取りとは異なるスレッドからデータベースに書き込むコードの一般的な例はありますか?また、スレッドの安全性をどのように確保できますか?.

私が持っていた1つの提案は、ContentProviderを使用することです。これは、複数のスレッドからのデータベースへのアクセスを処理するためです。これを見ていきますが、これはそのような問題を処理する推奨される方法ですか?私たちが前か後ろで話していることを考えると、かなり重いようです。

28
Pandalover

すべてのデータベースのオープンを確実に閉じ、(さらに重要なこと)これを保証することで、この同じ例外を解決し、各データベースインスタンスのスコープを、それを必要とするメソッドのみにローカルにしました。 ContentProviderは、複数のスレッドからデータベースにアクセスするときに使用するのに適した安全なクラスですが、適切なデータベースプラクティスを使用していることも確認してください。

  • データベースインスタンスをローカルに保つ(SQLiteDatabaseクラスメンバーはありません!)
  • 開かれたのと同じメソッドでデータベースのclose()を呼び出します
  • dbから取得したカーソルでclose()を呼び出します
  • sQLiteDatabseが持つ可能性のある苦情についてはLogCatを聞いてください
16
ubzack

最後にContentProviderを使用しました。これは問題を解決するように見えました。

28
Pandalover

いくつかのコードの前に、いくつかのアプローチを再開しましょう。

  • セマフォ:提示された最良のソリューション。それは問題の中心にあります:リソース共有!競合(_database is locked_)を回避して、データベースアクセスのロックを処理します。

  • Java同期:セマフォの実装の一種ですが、それほど洗練されていません。 synchronizedを使用すると、トランザクションに関連するいくつかのケースを簡単に解決できません。

  • ContentProviderContentProviderを実装して、一部の場合にのみ問題を解決します(または、カーペットの下で問題を一掃します) 。あなたはまだ同じ問題に直面するでしょう。違いは、ContentProviderパターンは、Sqliteデータベースにアクセスするときに、いくつかの間違いを犯さないように導くことです。 ContentProvider docs は、「使用が完全に自分のアプリケーション内であれば、SQLiteデータベースを使用するためにプロバイダーは必要ありません。」

  • ほぼ必須:dbインスタンスをローカルに保持し、同じメソッドでdbのclose()を呼び出しますfinallyステートメントを使用して開かれ、finallyステートメントを使用してカーソルのclose()などがalmostSqliteを使用する際の問題を避けるために必須です。

Moss で提示されたセマフォソリューションの例を示しましょう。これは CL。 から取得し、トランザクションをカバーするために改善されました。

_class DataAccess {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data readSomething(int id) {
        Cursor c = null;
        r.lock();
        try {
            c = getReadableDatabase().query(...);
            return c.getString(0);
        } finally {
            if (c != null) c.close();
            r.unlock();
        }
    }

    public void changeSomething(int id, int value) {
        w.lock();
        try {
            getWritableDatabase().update(...);
        } finally {
            w.unlock();
        }
    }

    private void beginTransactionWithSemaphores() {
        getWritableDatabase().beginTransactionWithListener(new SQLiteTransactionListener() {
            @Override
            public void onBegin() {
                w.lock();
            }

            @Override
            public void onRollback() {
                w.unlock();
            }

            @Override
            public void onCommit() {
                w.unlock();
            }
        });
    }
}
_
11
Italo Borssatto

SQLiteデータベースはファイルベースであり、マルチプロセスでアクセスできるように意図されていないことを考慮してください。 SQLiteとマルチプロセッシングを組み合わせる最適な手順は、各データベース関連のアクセスでセマフォ(aquire()、release())を使用することです。

グローバルセマフォを取得/解放するDbラッパーを作成すると、DBアクセスはスレッドセーフになります。確かに、これは、DBへのアクセスをキューイングしているため、ブートネックが発生する可能性があることを意味しています。したがって、データベースを変更する操作である場合にのみ、セマフォを使用してアクセスをラップできます。したがって、データベースを変更している間は誰もアクセスできず、書き込みプロセスが完了するまで待機できません。

9
Moss

データベースの読み取りおよび書き込み操作を同時に実行するために、複数のスレッドとDb接続を共有できませんでした。同期の概念を使用してDBの単一オブジェクトを作成する必要があり、一度に1つのタスクを実行します。オブジェクトと複数のスレッド内で共有されます。一度に1つのタスクを実行します。その後、他のタスクまたはDBの操作を開始します。コンテンツプロバイダーは、DBロックの問題の解決策ではありません。

import Java.util.concurrent.atomic.AtomicInteger;
import Android.database.sqlite.SQLiteDatabase;
import Android.database.sqlite.SQLiteOpenHelper;
import Android.util.Log;

public class DatabaseManager {

private AtomicInteger mOpenCounter = new AtomicInteger();

private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
//private static String DB_PATH = "";
//  private static String DB_NAME = "xyz.db";// Database name
private static String dbPathh;

public static synchronized void initializeInstance(SQLiteOpenHelper helper,
        String dbPath) {
    if (instance == null) {
        instance = new DatabaseManager();
        mDatabaseHelper = helper;
        dbPathh=dbPath;
    }
  }

public static synchronized DatabaseManager getInstance() {
    if (instance == null) {
        throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                " is not initialized, call initializeInstance(..) method first.");
    }

    return instance;
 }

  public synchronized SQLiteDatabase openDatabase(String thread) {

    if(mOpenCounter.get() == 0) {
        // Opening new database
        // mDatabase = mDatabaseHelper.getWritableDatabase();
        MyLog.e("Path Of DataBase", dbPathh);
        //  mDatabase=mDatabaseHelper.getWritableDatabase();
        mOpenCounter.incrementAndGet();
        mDatabase=SQLiteDatabase.openDatabase(dbPathh, null,   
 SQLiteDatabase.  CREATE_IF_NECESSARY|SQLiteDatabase.OPEN_READWRITE);   
        MyLog.e("Open Data Base", " New Connection created" +thread);
    }
    else{
        MyLog.e("Open Data Base", " Old Connection given " +thread);
    }
    //  Toast.makeText(NNacres.getConfig(), "open conn: present connection = 
   "   +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    return mDatabase;
   }

    public synchronized void closeDatabase() {
    MyLog.e("Close db connection", ""+mOpenCounter.get());

    if(mOpenCounter.get() == 1) {
        // Closing database

        mDatabase.close();
        mOpenCounter.decrementAndGet();

        Log.e("DB CLOSED", "DONE");
    }
    //Toast.makeText(NNacres.getConfig(), "close conn: after close =   
 " +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    }

    }

sQLiteOpenHelperクラスを拡張するYourSQLiteDataABseヘルパークラスでこのメソッドを記述します

     public SQLiteDatabase getWritableDatabase() {
DatabaseManager.initializeInstance(this,"data/data/your packgae name/databases/xyz");
    return DatabaseManager.getInstance().openDatabase(getClass().getSimpleName());

}



public static String getMyDbPath(String DB_NAME, Context context) {

    String myDbPath = context.getDatabasePath(DB_NAME).getPath();
    MyLog.e("DB Path: "+myDbPath);
    return myDbPath;
}
1
Ashish Saini