加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Sqlite多线程读写 wal机制

发布时间:2020-12-12 20:00:12 所属栏目:百科 来源:网络整理
导读:在项目中遇到一个sqlite3的错误,相信很多人都遇到过,如下: android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 1802) 06-25 14:18:46.340 E/CrashHandler( 2840): at android.database.sqlite.SQLiteConnection.nativeExecuteForChange
在项目中遇到一个sqlite3的错误,相信很多人都遇到过,如下:
android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 1802)
06-25 14:18:46.340 E/CrashHandler( 2840): at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
06-25 14:18:46.340 E/CrashHandler( 2840): at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:734)
06-25 14:18:46.340 E/CrashHandler( 2840): at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
06-25 14:18:46.340 E/CrashHandler( 2840): at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
06-25 14:18:46.340 E/CrashHandler( 2840): at android.database.sqlite.SQLiteDatabase.delete(SQLiteDatabase.java:1497)
发生这种的异常的原因大多都是因为sqlite数据库在多线程下读写造成的。

查了一下相关资料,发现sqlite还有一种叫wal的事务机制,在此记录,以备自己复习。

WAL的全称是Write Ahead Logging,它是很多数据库中用于实现原子事务的一种机制,SQLite在3.7.0版本引入了该特性。

下面列举Android各个版本对应的sqlite版本,其实3.0以上都用了sqlite3.7了,但android3.0以下版本的活跃用户还是有不少的。


SQLite 3.7.11:

19-4.4-KitKat
18-4.3-Jelly Bean
17-4.2-Jelly Bean
16-4.1-Jelly Bean

SQLite 3.7.4:

15-4.0.3-Ice Cream Sandwich
14-4.0-Ice Cream Sandwich
13-3.2-Honeycomb
12-3.1-Honeycomb
11-3.0-Honeycomb

SQLite 3.6.22:

10-2.3.3-Gingerbread
9-2.3.1-Gingerbread
8-2.2-Froyo

SQLite 3.5.9:

7-2.1-Eclair
4-1.6-Donut

3-1.5-Cupcake


在默认情况下,sqlite使用的是rollback journal机制实现原子事务。
rollback journal机制的原理是:在修改数据库文件中的数据之前,先将修改所在分页中的数据备份在另外一个地方,然后才将修改写入到数据库文件中;如果事务失败,则将备份数据拷贝回来,撤销修改;如果事务成功,则删除备份数据,提交修改。在这种机制下,读写事务不可以同时进行。你在读的时候可以有多个线程同时读,但在写的时候不能和读或者写同时进行,也即写时只能单线程操作。要不然就可能发生上面的error。在此种机制下,我们可以给多线程的读写加上同步synchronized。在写操作时,如果是同一个事务,尽量使用beginTransaction(),让写事务成为一个原子操作。

WAL机制的原理是:修改并不直接写入到数据库文件中,而是写入到另外一个称为WAL的文件中;如果事务失败,WAL中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。开启WAL模式可以提高写入数据库的速度,读和写之间不会阻塞,但是写与写之间依然是阻塞的。在项目中开启WAL模式,可以提高并发。由于使用WAL比rollback journal的模式减少了写的i/o,所以写入时速度较快,但是由于在读取数据时也需要读取WAL日志验证数据的正确性,所以读取数据相对要慢。所以大家也要根据自己应用的场景去使用这种模式。

在项目中可以通过以下方式打开wal模式。 SQLiteDatabase dbName = dbHelper.getWritableDatabase(); dbName.enableWriteAheadLogging(); 我们可以查看一下enableWriteAheadLoggin的源码: public boolean enableWriteAheadLogging() { synchronized (mLock) { throwIfNotOpenLocked(); if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) { return true; } if (isReadOnlyLocked()) { // WAL doesn't make sense for readonly-databases. // TODO: True,but connection pooling does still make sense... return false; } if (mConfigurationLocked.isInMemoryDb()) { Log.i(TAG,"can't enable WAL for memory databases."); return false; } // make sure this database has NO attached databases because sqlite's write-ahead-logging // doesn't work for databases with attached databases if (mHasAttachedDbsLocked) { if (Log.isLoggable(TAG,Log.DEBUG)) { Log.d(TAG,"this database: " + mConfigurationLocked.label + " has attached databases. can't enable WAL."); } return false; } mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; try { mConnectionPoolLocked.reconfigure(mConfigurationLocked); } catch (RuntimeException ex) { mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; throw ex; } } return true; } 需要注意的是,不能在有事务操作的时候进行关闭wal模式,不然会抛异常。 public void disableWriteAheadLogging() { synchronized (mLock) { throwIfNotOpenLocked(); if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) { return; } mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; try { mConnectionPoolLocked.reconfigure(mConfigurationLocked); } catch (RuntimeException ex) { mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; throw ex; } } } 如果使用了wal模式,在创建数据库时,它会在数据库目录上建一个xxx.db.wal的库。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读