今天项目刚灰度,就发现线上异常:
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (Sqlite code 5), (OS error - 11:Try again)
google了一下这个异常也没有好的解决方案,都说保证单例访问就可以,查看我的代码没有毛病,都是加了锁的,这就见鬼了
只能sqlite翻看源码
从异常上可以看出最后调用nativeExecuteForChangedRowCount
方法,找到源码看看android_database_SQLiteConnection.cpp
static jint nativeExecuteForChangedRowCount(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
// 会调用executeNonQuery
int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE ? sqlite3_changes(connection->db) : -1;
}
接下来看executeNonQuery
代码
static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
int err = sqlite3_step(statement);
if (err == SQLITE_ROW) {
throw_sqlite3_exception(env,
"Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
} else if (err != SQLITE_DONE) {
throw_sqlite3_exception(env, connection->db);
}
return err;
}
executeNonQuery
代码会调用throw_sqlite3_exception
抛出异常, 找到throw_sqlite3_exception
实现throw_sqlite3_exception
void throw_sqlite3_exception(JNIEnv* env, int errcode,
const char* sqlite3Message, const char* message) {
const char* exceptionClass;
switch (errcode) {
case SQLITE_IOERR:
exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
break;
case SQLITE_CORRUPT:
case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
break;
case SQLITE_CONSTRAINT:
exceptionClass = "android/database/sqlite/SQLiteConstraintException";
break;
case SQLITE_ABORT:
exceptionClass = "android/database/sqlite/SQLiteAbortException";
break;
case SQLITE_DONE:
exceptionClass = "android/database/sqlite/SQLiteDoneException";
break;
case SQLITE_FULL:
exceptionClass = "android/database/sqlite/SQLiteFullException";
break;
case SQLITE_MISUSE:
exceptionClass = "android/database/sqlite/SQLiteMisuseException";
break;
case SQLITE_PERM:
exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
break;
case SQLITE_BUSY:// 这个就是抛出JAVA数据库锁的异常
exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
break;
case SQLITE_LOCKED:
exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
break;
case SQLITE_READONLY:
exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
break;
case SQLITE_CANTOPEN:
exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
break;
case SQLITE_TOOBIG:
exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
break;
case SQLITE_RANGE:
exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
break;
case SQLITE_NOMEM:
exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
break;
case SQLITE_MISMATCH:
exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
break;
case SQLITE_INTERRUPT:
exceptionClass = "android/content/OperationCanceledException";
break;
default:
exceptionClass = "android/database/sqlite/SQLiteException";
break;
}
看到SQLITE_BUSY
就是我们对应的JAVA 异常 SQLITE_BUSY
什么意思呢,google 一下没找到,只能去官网上看了。
官方解析如下
The SQLITE_BUSY result code indicates that the database file could not be written (or in some cases read) because of concurrent activity by some other database connection, usually a database connection in a separate process.
翻译过来就是: SQLITE_BUSY 状态表示当前数据库文件不能写入,因为被其他进程占用
For example, if process A is in the middle of a large write transaction and at the same time process B attempts to start a new write transaction, process B will get back an SQLITE_BUSY result because SQLite only supports one writer at a time. Process B will need to wait for process A to finish its transaction before starting a new transaction. The sqlite3_busy_timeout() and sqlite3_busy_handler() interfaces and the busy_timeout pragma are available to process B to help it deal with SQLITE_BUSY errors.
An SQLITE_BUSY error can occur at any point in a transaction: when the transaction is first started, during any write or update operations, or when the transaction commits. To avoid encountering SQLITE_BUSY errors in the middle of a transaction, the application can use BEGIN IMMEDIATE instead of just BEGIN to start a transaction. The BEGIN IMMEDIATE command might itself return SQLITE_BUSY, but if it succeeds, then SQLite guarantees that no subsequent operations on the same database through the next COMMIT will return SQLITE_BUSY.
所以从JAVA层处理该异常可以 try 一下。或则稍后重试。