Блокировка и блокировка базы данных 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)

Есть ли у кого-нибудь общий пример кода, который записывает в базу данных из другого потока, кроме одного, и как мы можем обеспечить безопасность потоков.

Одно из предложений, которое у меня было, - использовать ContentProvider, поскольку это будет обрабатывать доступ к базе данных из нескольких потоков. Я собираюсь посмотреть на это, но является ли это рекомендуемым методом решения такой проблемы? Это кажется довольно тяжеловесным, потому что мы говорим о нем спереди или сзади.

Ответ 1

Я решил это же исключение, просто удостоверившись, что все мои открытые базы данных закрываются, и (что более важно), чтобы гарантировать это, делая область каждого экземпляра базы данных локальной ТОЛЬКО ТОЛЬКО методу, который ему нужен. ContentProvider - хороший, безопасный класс для использования при доступе к db из нескольких потоков, но также убедитесь, что вы используете хорошие практики db:

  • Хранить экземпляры db локально (без элементов класса SQLiteDatabase!)
  • вызов close() на db в том же методе, в котором он был открыт
  • вызов close() на курсорах, которые вы получаете из db
  • послушайте LogCat для любых жалоб, которые SQLiteDatabse может иметь

Ответ 2

В конце мы использовали ContentProvider. Это, похоже, прояснило проблемы.

Ответ 3

Перед некоторым кодом возобновите некоторые из подходов:

  • Семафоры: безусловно, лучшее из представленных решений. Это лежит в основе проблемы: обмен ресурсами! Он будет рассматривать блокировку доступа к базе данных, избегая конфликтов (database is locked).

  • Java-синхронизация: вид реализации семафора, но менее сложный. Используя synchronized, вы не сможете легко решить некоторые случаи, связанные с транзакциями.

  • ContentProvider: реализовать ContentProvider решить проблему только для некоторых случаев (или поднять проблему под ковром). Вы столкнетесь с теми же проблемами. Разница заключается в том, что шаблон ContentProvider поможет вам не совершать некоторые ошибки commom при доступе к базе данных Sqlite. ContentProvider docs говорит: "Вам не нужен провайдер для использования базы данных SQLite, если использование целиком находится в вашем собственном приложении".

  • Почти обязательный параметр: держите локальные экземпляры db локально, вызывайте close() на db тем же методом, в котором он был открыт с помощью операторов finally, close() на курсорах с использованием операторов finally и т.д. почти обязательно, чтобы избежать проблем с использованием Sqlite.

Покажем пример решения семафора, представленного Мосом 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();
            }
        });
    }
}

Ответ 4

Учитывайте, что базы данных SQLite основаны на файлах и не могут быть доступны для доступа несколькими способами. Лучшей процедурой смешивания SQLite с многопроцессорной обработкой является использование семафоров (aquire(), release()) в каждом доступе, связанном с базой данных.

Если вы создаете оболочку Db, которая использует/освобождает глобальный семафор, ваш доступ к БД будет потокобезопасным. На самом деле это означает, что вы можете получить загрузочное окно, потому что вы в очереди на доступ к БД. Таким образом, вы можете только обернуть доступ с помощью семафоров, если это операция, которая изменяет базу данных, поэтому, пока вы изменяете db, никто не сможет получить к ней доступ и дождитесь завершения процесса записи.

Ответ 5

Мы не могли совместно использовать соединение 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();
    }

    }

и напишите этот метод в вашем вспомогательном классе YourSQLiteDataABse, который расширяет класс SQLiteOpenHelper

     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;
}

Ответ 6

Вы должны вызывать getWritableDatabase() из функции, а не из конструктора класса-помощника db. Если объект класса-помощника db создается с помощью SQLiteDatabase.openOrCreateDatabase(DB_PATH, null); или аналогичного, а затем getWritableDatabase() вызывается из функции, он попытается сделать синхронный вызов в БД, вызвав исключение блокировки БД.

Ответ 7

Вы говорите об одном действии пользователя, которое внутри вашей программы вызывает запуск нескольких потоков, более одного из которых может обращаться к базе данных в режиме обновления?

Плохой дизайн, период. Вы не можете узнать, в каком порядке потоки будут запланированы вашей ОС (/VM), и, следовательно, вы не сможете узнать, в каком порядке будет осуществляться доступ к базе данных, и это, скорее всего, подразумевает что вам не нужно знать, что доступ к базе данных будет всегда происходить в том порядке, в котором вы ожидаете.

Все обращения к базе данных, сгенерированные/поступающие от какого-либо действия пользователя, должны выполняться в одном потоке.