Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite异常整理 #27

Open
zengjingfang opened this issue Apr 23, 2018 · 2 comments
Open

SQLite异常整理 #27

zengjingfang opened this issue Apr 23, 2018 · 2 comments

Comments

@zengjingfang
Copy link
Owner

zengjingfang commented Apr 23, 2018

android.database.sqlite.SQLiteDiskIOException: disk I/O error - SQLITE_IOERR_WRITE (Sqlite code 778), (OS error - 9:Bad file descriptor)
at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:563)
at android.database.sqlite.SQLiteSession.endTransactionUnchecked(SQLiteSession.java:437)
at android.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:401)
at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:541)
at com.xtc.im.core.push.store.dao.ServerConfigDao.update(Unknown Source)
at com.xtc.im.core.push.domain.DomainManager.updateOnConnectSuccess(Unknown Source)
at com.xtc.im.core.push.PushService$1$1.run(Unknown Source)
at android.os.Handler.handleCallback(Handler.java:743)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:150)
at android.os.HandlerThread.run(HandlerThread.java:61)

@zengjingfang zengjingfang created this issue from a note in Android开发历险记 (To Do) Apr 23, 2018
@zengjingfang
Copy link
Owner Author

zengjingfang commented Apr 27, 2018

异常详情

android.database.sqlite.SQLiteDiskIOException: disk I/O error - SQLITE_IOERR_WRITE (Sqlite code 778), (OS error - 9:Bad file descriptor)
at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:563)
at android.database.sqlite.SQLiteSession.endTransactionUnchecked(SQLiteSession.java:437)
at android.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:401)
at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:541)
at com.xtc.im.core.push.store.dao.ServerConfigDao.update(Unknown Source)
at com.xtc.im.core.push.domain.DomainManager.updateOnConnectSuccess(Unknown Source)
at com.xtc.im.core.push.PushService$1$1.run(Unknown Source)
at android.os.Handler.handleCallback(Handler.java:743)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:150)
at android.os.HandlerThread.run(HandlerThread.java:61)

资料查询

https://www.sqlite.org/rescode.html

官方解释

The SQLITE_IOERR_WRITE error code is an extended error code for SQLITE_IOERR indicating an I/O error in the VFS layer while trying to write into a file on disk. This error might result from a hardware malfunction or because a filesystem came unmounted while the file was open. This error should not occur if the filesystem is full as there is a separate error code (SQLITE_FULL) for that purpose.

获取信息

  • 设备故障导致
  • 文件打开时正在卸载文件系统导致

@zengjingfang zengjingfang moved this from To Do to Done in Android开发历险记 Apr 27, 2018
@zengjingfang zengjingfang moved this from Done to running in Android开发历险记 Apr 27, 2018
@zengjingfang zengjingfang changed the title SQLiteDiskIOException: disk I/O error - SQLITE_IOERR_WRITE SQLite异常整理 Apr 27, 2018
@zengjingfang
Copy link
Owner Author

zengjingfang commented Apr 27, 2018

异常详情

pro5 出现1次

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase: /data/user/0/com.xtc.watch/databases/xtc_im.db
	at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55)
	at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:522)
	at com.xtc.im.core.push.store.dao.ServerConfigDao.update(Unknown Source)
	at com.xtc.im.core.push.domain.DomainManager.updateOnConnectFailed(Unknown Source)
	at com.xtc.im.core.push.PushService$1$4.run(Unknown Source)
	at android.os.Handler.handleCallback(Handler.java:751)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:154)
	at android.os.HandlerThread.run(HandlerThread.java:61)

应用代码

public void update(DBServerConfigEntity entity) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
       // 事务开始
        db.beginTransaction();
        Cursor cursor = null;
        try {
            cursor = db.query(DBServerConfigEntity.TABLE, null, DBServerConfigEntity.KEY_ID + "=?", new String[]{String.valueOf(entity.getId())}, null, null, null);
            DBServerConfigEntity oldEntity = null;
            if (cursor == null) {
                LogUtil.e(TAG,"cursor is null ,the config we update is not exist !!!");
                return;
            }
            if (cursor.moveToFirst()) {
                oldEntity= serverConvertToEntity(cursor);
            }
            if (oldEntity == null) {
                LogUtil.e(TAG,"old server config is null ,the config we update is not exist !!!");
                return;
            }

            ContentValues values = serverConvertToValues(entity, oldEntity);
            db.update(DBServerConfigEntity.TABLE, values, DBServerConfigEntity.KEY_ID + "=?", new String[]{String.valueOf(entity.getId())});
           // 事务 成功
            db.setTransactionSuccessful();
        } catch (Exception e) {
            LogUtil.e(TAG, e);
        } finally {
            // 事务结束
            db.endTransaction();
            if (cursor != null) {
                cursor.close();
            }
        }

    }

源码追踪

SQLiteDatabase.java

public void endTransaction() {
    acquireReference();
    try {
        getThreadSession().endTransaction(null);
    } finally {
        releaseReference();
    }
}

public void acquireReference() {
    synchronized(this) {
        if (mReferenceCount <= 0) {
            throw new IllegalStateException(
                    // 抛出异常
                    "attempt to re-open an already-closed object: " + this);
        }
        mReferenceCount++;
    }
}

原因分析

我们和数据库只能有一条连接,即我们在调用dbHelper.getWritableDatabase()返回的是同一个对象,也就意味着我们在不同的线程需要用同一个dbHelper获取db。否则会报:android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 。

有可能在别处调用了db.close方法,最终会调用releaseReference方法,导致mReferenceCount减一,当上述执行endTransaction时发现mReferenceCount<=0,就抛出了该异常。

解决方案

  • 不关法:用完不关闭(调用db.close方法),连接一直开着,不过存在性能的影响,观察源码可知。
    public void releaseReference() {
        boolean refCountIsZero = false;
        synchronized(this) {
            refCountIsZero = --mReferenceCount == 0;
        }
        if (refCountIsZero) {
            // 当应用层没有连接的时候调用这里,去释放数据库连接
            onAllReferencesReleased();
        }
    }
  • 计数法:

在外部添加一个获取连接的管理类,并进行计数,每打开一个加1,用完减1,如果没有了则调用关闭连接,调用db.close。
参考文章android sqlite数据库并发问题的详细描述和解决方案

知识扩展

AndroidSQLite的事务机制,保障了数据修改的准确性,避免出现脏数据。通过beginTransaction开启一个事务,然后执行自己的数据库操作语句,如果顺利执行完,就会执行setTransactionSuccessful,此时设置了一个标志位 mTransactionStack.mMarkedSuccessful = true。最后执行endTransaction,会进行前面标志位的判断,如果为true表明此次操作没有问题。就执行“COMMIT”提交数据,否则执行“ROLLBACK”,将数据回滚到本次事务操作之前的状态。

SQLiteSession#setTransactionSuccessful

    public void setTransactionSuccessful() {
        throwIfNoTransaction();
        throwIfTransactionMarkedSuccessful();
       // 事务顺利结束 在此设置成功的标志位
        mTransactionStack.mMarkedSuccessful = true;
    }

SQLiteSession#endTransactionUnchecked

private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
        if (cancellationSignal != null) {
            cancellationSignal.throwIfCanceled();
        }

        final Transaction top = mTransactionStack;
        boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;

        RuntimeException listenerException = null;
        final SQLiteTransactionListener listener = top.mListener;
        if (listener != null) {
            try {
                if (successful) {
                    listener.onCommit(); // might throw
                } else {
                    listener.onRollback(); // might throw
                }
            } catch (RuntimeException ex) {
                listenerException = ex;
                successful = false;
            }
        }

        mTransactionStack = top.mParent;
        recycleTransaction(top);

        if (mTransactionStack != null) {
            if (!successful) {
                mTransactionStack.mChildFailed = true;
            }
        } else {
            try {
                if (successful) {
                   // 事务成功 则提交
                    mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
                } else {
                   // 事务失败 则回滚
                    mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
                }
            } finally {
                releaseConnection(); // might throw
            }
        }

        if (listenerException != null) {
            throw listenerException;
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

1 participant