30

我们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)

有没有人有一个通用的代码示例,该代码从一个不同的线程写入数据库,而不是一个读取,我们如何确保线程安全。

我的一个建议是使用 a ContentProvider,因为这将处理来自多个线程的数据库访问。我要看看这个,但这是处理此类问题的推荐方法吗?考虑到我们在前面或后面谈论,它似乎相当重量级。

4

7 回答 7

28

我们最后使用了一个ContentProvider。这似乎解决了问题。

于 2010-04-23T09:42:55.957 回答
16

我解决了同样的异常,只是确保我所有打开的数据库都关闭,并且(更重要的是)确保这一点,使每个数据库实例的范围仅对需要它的方法本地。ContentProvider 是一个很好的、安全的类,可以在从多个线程访问数据库时使用,但也要确保您使用了良好的数据库实践:

  • 将数据库实例保持在本地(没有 SQLiteDatabase 类成员!)
  • close()以打开数据库的相同方法调用数据库
  • 调用close()从数据库获得的游标
  • 收听 LogCat 以了解 SQLiteDatabse 可能有的任何投诉
于 2012-02-28T16:29:59.837 回答
12

在一些代码之前,让我们恢复一些方法:

  • 信号量:迄今为止最好的解决方案。它进入了问题的核心:资源共享!它将处理数据库访问的锁定,避免冲突(database is locked)。

  • Java 同步:一种信号量实现,但不那么复杂。使用synchronized你不会轻易解决一些涉及交易的案例。

  • ContentProviderContentProvider仅在某些情况下解决问题(或将问题扫到地毯下)。你仍然会面临同样的问题。不同的是,该ContentProvider模式将指导您在访问 Sqlite 数据库时不要犯一些常见的错误。ContentProvider 文档说:“如果使用完全在您自己的应用程序中,您不需要提供程序来使用 SQLite 数据库。”

  • 几乎是强制性的:将数据库实例保持在本地,close()打开数据库的相同方法调用数据库,使用finally语句close()在游标上finally几乎是强制性的,以避免使用 Sqlite 出现问题。

让我们展示一个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();
            }
        });
    }
}
于 2013-11-12T20:54:47.843 回答
9

考虑到 SQLite 数据库是基于文件的,并且不能以多进程方式访问。将 SQLite 与多处理混合的最佳过程是在每个与数据库相关的访问中使用信号量(aquire()、release())。

如果您创建一个获取/释放全局信号量的数据库包装器,您的数据库访问将是线程安全的。实际上,这意味着您可能会因为您正在排队访问数据库而受到限制。因此,此外,如果它是更改数据库的操作,则只能使用信号量来包装访问,因此当您更改数据库时,没有人将能够访问它并等待写入过程完成。

于 2010-04-17T11:36:54.643 回答
1

我们无法与多个线程共享 Db 连接以同时在数据库中执行读写操作。我们将不得不使用同步概念制作 DB 的单个对象,我们将一次执行一项任务。我们将使用单例模式来制作 DB对象,它将在多个线程中共享。一次将执行单个任务。然后我们将在 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;
}
于 2014-07-11T12:23:07.050 回答
-3

您必须getWritableDatabase()从函数调用,而不是从 db 助手类的构造函数调用。如果 db helper 类对象是使用 SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);或类似创建的,然后getWritableDatabase()从函数中调用,它将尝试对 DB 进行同步调用,从而导致 DB 锁定异常。

于 2011-11-29T16:56:42.177 回答
-6

您是在谈论在您的程序内部导致多个线程运行的单个用户操作,其中多个线程可能正在更新模式下访问数据库?

那是糟糕的设计,时期。您无法知道操作系统 (/VM) 将按什么顺序调度线程,因此您无法知道数据库访问将以什么顺序发生,这很可能意味着您无法知道数据库访问将始终按照您期望的顺序发生。

由某些用户操作生成/来自某些用户操作的所有数据库访问都应在一个线程中完成。

于 2010-04-15T20:29:20.213 回答