SQLite异常:数据库被锁定问题

14

我的安卓应用中的SQLite数据库出现了问题。似乎这种情况偶尔发生,我无法再现,但这是Android Market提供的报告。

基本上,我先有一个闪屏活动,它通过检查数据库中是否有所有所需数据来启动。如果没有,它将在异步线程中安装数据并关闭数据库连接。

然后只有主活动才能被启动,同时打开、读写数据库。

下面是从闪屏活动的onCreate方法执行的代码:

dbWord = new WordDBAdapter(this, myUI);
dbWord.open();
if (dbWord.isDataInstalled()) {
    dbWord.close();
    databaseInstalled = true;
    clickToStartView.setText(myUI.PRESS_TO_START);
    clickToStartView.startAnimation(myBlinkAnim);
} else {
    // If not, try to install in asynchronous thread
    progressDialog = new ProgressDialog(this);
    progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    progressDialog.setMessage(myUI.INSTALLING_DB);
    progressDialog.setCancelable(false);
    new InstallDBData().execute("");
}

异步线程的代码为:
private class InstallDBData extends AsyncTask<String, Integer, Integer> {

    @Override
    protected Integer doInBackground(String... arg0) {
        progressDialog.setProgress(0);
        dbWord.installData(0, getApplicationContext());
        progressDialog.setProgress(20);
        dbWord.installData(1, getApplicationContext());
        progressDialog.setProgress(40);
        dbWord.installData(2, getApplicationContext());
        progressDialog.setProgress(60);
        dbWord.installData(3, getApplicationContext());
        progressDialog.setProgress(80);
        dbWord.installData(4, getApplicationContext());
        progressDialog.setProgress(100);
        return 1;
    }

    protected void onPreExecute() {
        progressDialog.show();
    }

    protected void onPostExecute(Integer x) {
        dbWord.close();
        progressDialog.hide();
        databaseInstalled = true;
        clickToStartView.setText(myUI.PRESS_TO_START);
        clickToStartView.startAnimation(myBlinkAnim);
    }
}

这些是WordDBAdapter类的重要组成部分,该类也被主活动使用:

public class WordDBAdapter {
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    public WordDBAdapter open() throws android.database.SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() {
        mDbHelper.close();
    }
    ...
}

我得到了以下的异常,它们很相似但有不同的信息:

第一种错误信息:

java.lang.RuntimeException: Unable to start activity 
  ComponentInfo{example.flashcards.medical/com.example.flashcards.common.SplashWindow}: 
  android.database.sqlite.SQLiteException: database is locked: BEGIN EXCLUSIVE;
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1830)
....
Caused by: android.database.sqlite.SQLiteException: database is locked: BEGIN EXCLUSIVE;
at android.database.sqlite.SQLiteDatabase.native_execSQL(Native Method)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1870)
at android.database.sqlite.SQLiteDatabase.beginTransactionWithListener(SQLiteDatabase.java:602)

第二种错误信息:

java.lang.RuntimeException: Unable to start activity 
  ComponentInfo{example.flashcards.medical/com.example.flashcards.common.SplashWindow}: 
  android.database.sqlite.SQLiteException: database is locked
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1768)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1784)
....
Caused by: android.database.sqlite.SQLiteException: database is locked
at android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method)
at android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.java:1987)
at android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:1855)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:820)
at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:854)

我真的不想创建ContentProvider,因为我认为如果有更简单的解决方案,那就过度了。而且只有这个应用程序可以访问数据库。

有没有任何建议可以解决这个问题?

7个回答

19

经过一些努力,我终于解决了问题(我的应用在 Android 2.3 上运行良好,但在 HoneyComb 平板电脑上运行时出现了数据库锁错误)。 我使用信号量(在关键部分使用锁)解决了这个问题。

示例1

public class DbExp extends SQLiteOpenHelper{
    public static String Lock = "dblock";
    private static final String DATABASE_NAME = "db.db";
    private static final String TABLE_NAME = "table_name";

    public void delete(Context mContext){
        synchronized(Lock) {
            SQLiteDatabase db  = getWritableDatabase();
            db.delete(TABLE_NAME, null, null);
            db.close();
        }
    }

    public void insert(){
        synchronized(Lock) {
            SQLiteDatabase db  = getWritableDatabase();
            db.insert(TABLE_NAME, ..., ...);
            db.close();
        }
    }

}

例子2

public class DB {
    public static String Lock = "dblock";
    private static final String DATABASE_NAME = "db.db";
    private static final String TABLE_NAME = "table_name";

    public void delete(Context mContext){
        synchronized(Lock) {
            SQLiteDatabase db  = mContext.openOrCreateDatabase(DATABASE_NAME, Context.MODE_PRIVATE, null);
            db.delete(TABLE_NAME, null, null);
            db.close();
        }
    }

    public void insert(Context mContext){
        synchronized(Lock) {
            SQLiteDatabase db  = mContext.openOrCreateDatabase(DATABASE_NAME, Context.MODE_PRIVATE, null);
            db.insert(TABLE_NAME, ..., ...);
            db.close();
        }
    }


}

希望这能对未来的任何人有所帮助 :)


我在我的应用程序数据库访问中使用了这种方式,但在我的情况下无法正常工作,请查看我的问题链接在这里... - MobileEvangelist

5
如果您使用的是片段,并且发生方向更改问题,那么这是因为您有两个相同片段的实例,它们都有自己的Db帮助程序类实例。要使其正常工作,您只能在方向更改时销毁您的片段并重新构建它,或者找到一种销毁Db帮助程序类的方法。这仅适用于具有方向问题的片段。我不得不进行大量故障排除才能找出解决方法,希望对某些人有所帮助。

5

这是我的原因:

在我的函数调用中,我忘记结束事务,在向数据库插入数据后。所以当我尝试从另一个活动插入另一条记录时,我遇到了数据库锁定异常。添加以下语句解决了我的问题。

sqlDB.endTransaction();

太棒了。我已经为此苦苦挣扎了几个小时……你真是拯救了我的一天,兄弟。 - Jeremy Luisetti

1
我建议您在主线程中关闭数据库,并在doInBackground方法中打开和关闭它:
    @Override
    protected Integer doInBackground(String... arg0)
    {
        db.open();
        try {
        // your code here
        } finally {
            db.close()
        }            
        return 1;
    }

此外,您不应该直接从AsyncTask调用UI方法,因为UI不是线程安全的。相反,应该从doInBackground调用updateProgress方法,并从onProgressUpdate更新您的progressDialog,因为onProgressUpdate是从主线程调用的。


1

看起来这是一个较旧的问题,但也许这会对某些人有所帮助。我遇到了同样的问题,我在一个数据库中加载大约十几个表格,经常会崩溃,不是总是,但有时候会。

最后,这可能有点hackey,但我只是为每个表格创建了一个单独的数据库。现在我有了十几个数据库,每个数据库都有一个表格。但是,没有更多的异常。代码正确,创建、打开和关闭都正确,只是似乎在某个地方发生了冲突。

就像我说的,你可能认为它很hackey,但它解决了我的问题。

db


8
请注意,使用这种方法将无法创建涉及分离数据库中表的事务。 - Dmitry Pashkevich
1
使用每个表一个数据库会破坏关系型数据库的许多目的,例如在多个表之间执行连接或具有跨多个表的事务。 - Knuckles the Echidna

0
也许这是因为您没有正确打开或创建数据库。如果是这种情况,请尝试一下。
mDb = mCtx.openOrCreateDatabase(DatabaseName, SQLiteDatabase.CREATE_IF_NECESSARY, null);

在DatabaseHelper类中:
public DatabaseHelper(Context aContext)
{       
    mDb = aContext.openOrCreateDatabase(DatabaseName, SQLiteDatabase.CREATE_IF_NECESSARY, null);    
    OpenHelper openHelper = new OpenHelper(aContext, mDb );     
    mDb = openHelper.getWritableDatabase();
}

这个应该放在哪里?是在 public WordDBAdapter open() 函数内部吗? - Sandy
我已经在我的DatabaseHelper类的构造函数中完成了它。我为您编辑了我的答案。 - Khawar
你确定这是必要的吗?根据Android文档中SQLiteOpenHelper的描述:“你需要创建一个子类实现onCreate(SQLiteDatabase)、onUpgrade(SQLiteDatabase, int, int)和可选的onOpen(SQLiteDatabase)方法,这个类会负责打开数据库(如果存在),如果不存在则创建它,并在必要时进行升级。使用事务来确保数据库始终处于合理状态。”我的DatabaseHelper类扩展了SQLiteOpenHelper,因此应该已经涵盖了打开和创建(如果需要)的功能,对吧? - Sandy
2
Khawar,我注意到你在DatabaseHelper()构造函数中没有调用super(context, DATABASE_NAME, null, DATABASE_VERSION); - Someone Somewhere
@Khawar不在意 - Denny

0
这几行代码解决了我的(同样的)问题:

(我在此之前已经打开了我的交易...)

writableDatabase.endTransaction();
writableDatabase.close();
dbHelper.close();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接