例外情况“正在进行事务时,无法启用或禁用写入前进记录(WAL)模式”,同时写入“互斥独占”

时间:2015-12-03 06:23:50

标签: android sqlite

在我的Android项目中,有许多数据库在不同的线程中读写。为了提高阅读速度,我使用了SQLiteDatabase.enableWriteAheadLogging,有时还有异常

  

java.lang.IllegalStateException:Write Ahead Logging(WAL)模式不能   在正在进行的事务中启用或禁用。   完成所有事务并释放所有活动数据库连接   第一

得到了。但是在检查代码之后,所有的写操作都是互斥的。 所以我写了一个示例代码来重现问题。通话后

new DbTest().doTest();

异常将在3-5分钟内发生。这是测试代码:

    private class DbTest extends Handler{
        Thread readThrd = null;
        Thread writeThrd = null;

        @Override
        public void handleMessage(Message msg){
            if (msg.what == 100)
                readThrd = null;
            else if (msg.what == 200)
                writeThrd = null;

            if (readThrd == null && writeThrd == null){
                doTest();
            }
        }

        private DatabaseHelper mDbHelper;
        public void doTest(){
            mDbHelper = new DatabaseHelper(MyActivity.this);
            SQLiteDatabase db = mDbHelper.getWritableDatabase();
            db.execSQL("delete from " + TABLE_NAME + " where 1");
            //new ReadThread(mDbHelper).start();
            readThrd = new ReadThread(mDbHelper, TABLE_NAME, this);
            writeThrd = new WriteThread(mDbHelper, TABLE_NAME, this);
            writeThrd.start();
            readThrd.start();
        }
    }

    private class ReadThread extends Thread{
        DatabaseHelper mDbHelper = null;
        private String tableName;
        private Handler hdl;

        public ReadThread(DatabaseHelper db, String tblName, Handler hdl){
            mDbHelper = db;
            tableName = tblName;
            this.hdl = hdl;
        }

        public void run(){
            Log.e("DBG", "Read Start");
            int count = 0;
            while(count++ < 300) {
                //Log.e("DBG", "read open:\t" + count);
                try {
                    mDbHelper.addReadingCount();
                    SQLiteDatabase db = mDbHelper.getReadableDatabase();
                    for (int i = 0; i < 100; ++i) {
                        String sql = "select count(*) from " + tableName;
                        //String sql = "select * from " + tableName;
                        Cursor c = db.rawQuery(sql, null);
                        int len = 1;
                        if (null != c && c.moveToFirst()) {
                            len = c.getInt(0);
                            /*
                            do {
                                len = c.getInt(0);
                            if (len % 10 == 0 ) {
                                //Log.e("DBG", "read:\t" + len);
                                break;
                            }
                            } while (c.moveToNext() && len < 10);
                            */
                        }
                        if (null != c) {
                            c.close();
                        }
                    }
                }finally{
                    mDbHelper.removeReadingCount();
                }
                }
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){

                }
                //Log.e("DBG", "read close:\t" + count);
                //db.close();
            Log.e("DBG", "Read End");
            hdl.sendEmptyMessageDelayed(100, 1000);
        }
    }

    private class WriteThread extends Thread{
        DatabaseHelper mDbHelper = null;
        String tableName;
        Handler hdl;

        public WriteThread(DatabaseHelper db, String tableName, Handler hdl){
            mDbHelper = db;
            this.tableName = tableName;
            this.hdl = hdl;
        }

        public void run(){
            Log.e("DBG", "Write Start");
            int count = 0;
            while(count++ < 101) {
                synchronized (write) {
                    SQLiteDatabase db = mDbHelper.getWritableDatabase();
                    if (db.inTransaction()){
                        db.close();
                        Log.d("DBG", "Close by transaction");
                        db = mDbHelper.getWritableDatabase();
                    }
                    /*
                    while(db.inTransaction()){
                        Log.e("DBG", "Wait Transaction");
                        try {
                            Thread.sleep(50);
                        }catch (InterruptedException e){

                        }
                    }
                    */
                    db.enableWriteAheadLogging();
                    //Log.e("DBG", "Write open:\t" + count + ", " + b);
                    try {
                        db.beginTransactionNonExclusive();
                        long now = new Date().getTime();
                        for (int i = 0; i < 1000; ++i) {
                            String sql = "insert into " + tableName + " (NAME, INFO) values ('%1$s', '%2$s')";
                            String name = "" + now + "-" + i;
                            sql = String.format(sql, name, "info");
                            db.execSQL(sql);
                        }
                        //Log.e("DBG", "Write close:\t" + count);
                        db.setTransactionSuccessful();
                    }finally {
                        db.endTransaction();
                    }
                    //db.close();
                    mDbHelper.closeDb(db);
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                }
            }
            SQLiteDatabase db = mDbHelper.getWritableDatabase();
            db.close();
            Log.e("DBG", "Write End");
            hdl.sendEmptyMessageDelayed(200, 1000);
        }
    }

    private static final String  DATABASE_NAME = "info.db";
    private static final int DATABASE_VERSION = 1;
    private static final String TABLE_NAME= "Info";
    private static final String TABLE_NAME2 = "Infoa";

    private static class DatabaseHelper extends SQLiteOpenHelper {
        volatile Vector<Integer> readingCount = new Vector<Integer>();
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            //创建用于存储数据的表
            db.execSQL("Create table " + TABLE_NAME + "( _id INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, INFO TEXT);");
            db.execSQL("Create table " + TABLE_NAME2 + "( _id INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, INFO TEXT);");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
            onCreate(db);
        }

        public boolean isReading(){
            return (readingCount.size() != 0);
        }

        public void addReadingCount(){
            int sz = readingCount.size();
            readingCount.add(1);
        }

        public void removeReadingCount(){
            int sz = readingCount.size();
            if (readingCount.size() > 0)
                readingCount.remove(0);
        }

        public void closeDb(SQLiteDatabase db){
            /*
            synchronized (readingCount) {
                if (readingCount.size() > 0)
                    return;
                else
                    db.close();
            }*/

            synchronized (readingCount) {
                while(readingCount.size() > 0) {
                    try {
                        //Log.e("DBG", "WAIT START");
                        readingCount.wait(30);
                        //Log.e("DBG", "WAIT STOP");
                    }catch (InterruptedException e) {

                    }
                }
                //Log.e("DBG", "Closed" + readingCount.size());
                db.close();
            }
        }
    }
}

在上面的代码中,所有写操作都被synchronized(write)锁定,但为什么是异常

  

java.lang.IllegalStateException:Write Ahead Logging(WAL)模式不能   在正在进行的事务中启用或禁用。   完成所有事务并释放所有活动数据库连接   第一

还会发生吗? 如果删除Thread.Sleep(),则不会发生异常,这很奇怪。有人帮忙吗?我也试图不关闭数据库,但仍存在相同的异常。

1 个答案:

答案 0 :(得分:3)

WAL设置是永久性的,因此您只需启用一次。

启用它的正确方法是在setWriteAheadLoggingEnabled()中调用onConfigure() callback