Android Sqlite并发问题

背景

  • 我们的项目中使用的是ormlite的加密框架sqlcipher来进行数据库操作的

多进程操作同一个数据库文件出现了问题

net.sqlcipher.database.SQLiteException: error code 5: database is locked
                                                           at net.sqlcipher.database.SQLiteStatement.native_execute(Native Method)
                                                           at net.sqlcipher.database.SQLiteStatement.executeInsert(SQLiteStatement.java:84)
                                                           at com.j256.ormlite.sqlcipher.android.AndroidDatabaseConnection.insert(AndroidDatabaseConnection.java:158)
                                                           at com.j256.ormlite.stmt.mapped.MappedCreate.insert(MappedCreate.java:91)
                                                           at com.j256.ormlite.stmt.StatementExecutor.create(StatementExecutor.java:450)
                                                           at com.j256.ormlite.dao.BaseDaoImpl.create(BaseDaoImpl.java:310)
                                                           at com.xtc.database.encrypt.OrmLiteDao.insert(OrmLiteDao.java:99)
                                                           at com.xtc.bigdata.report.db.ReportDao.insert(ReportDao.java:46)
                                                           at com.xtc.bigdata.report.db.UserBehaviorProvider.insert(UserBehaviorProvider.java:113)
                                                           at android.content.ContentProvider$Transport.insert(ContentProvider.java:264)
                                                           at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:163)
                                                           at android.os.Binder.execTransact(Binder.java:565)

如上异常堆栈中的错误信息error code 5: database is locked,经过查找发现code为5代表sqlite中的SQLITE_BUSY异常,详见:https://www.sqlite.org/rescode.html#busy,这里面说,SQLITE_BUSY(5)异常是一个数据库文件在被其他不同的数据库连接进行并发操作的时候写操作将补发继续,通常是多个进程的不同数据库连接对同一个数据库进行并发操作,例如进程A在进行耗时的数据库事务,而于此同时进程B也要进行一个数据库事务,这时候进程B就会直接返回SQLITE_BUSY的错误码,因为sqlite只能支持同一个时刻只能有一个写操作,所以解决这个问题的方法就是避免不同进程分别对同一个数据库各自开启一个database connection,并且对相同的数据库进行并发操作,如果有这种需求,那么应该全部都交给一个进程来对数据库进行操作,其他的进程想操作这个数据库就通过contentprovider的方式来实现数据共享,使用contentprovider的方式是最安全的,如果是通过shareUserId的方式来实现数据库共享也是不安全的,因为:

Context thdContext = null;  
try {  
    thdContext = createPackageContext(  
            "com.example.testdatabase",  
            Context.CONTEXT_IGNORE_SECURITY);  
    String dbPath = thdContext.getDatabasePath("BookStore.db")  
            .getAbsolutePath();  
    SQLiteDatabase db = SQLiteDatabase.openDatabase(dbPath,  
            null, SQLiteDatabase.OPEN_READWRITE);  
    Cursor cursor = db.query("Book", null, null, null, null,  
            null, null);  
    if (cursor.moveToFirst()) {  
        do {  
            String name = cursor.getString(cursor  
                    .getColumnIndex("name"));  
            Log.d(TAG, "name: " + name);  
        } while (cursor.moveToNext());  
    }  
} catch (NameNotFoundException e) {  
    e.printStackTrace();  
}  

SQLiteDatabase.openDatabase会创建一个数据库实例SQLiteDatabase,如果在不同的进程如果通过shareuserid来实现数据库共享,那么会造成每一个进程都有SQLiteDatabase对象,在并发操作的时候也有可能会出现如上问题,所以还是推荐使用contentprovider的方式来实现数据库共享,必须注意contentprovider必须只有宿主app进程来维护,其他的进程就通过调用宿主app进程的contentprovider暴露出去的接口来实现对宿主app进程的数据库的操作,实际上这时候的数据库操作就都是由宿主app进程来操作的了,就不会出现如上的异常

拓展

  • 上面提及的数据库操作异常的code是5,对应的是SQLITE_BUSY,这里还有一个相似的数据库操作异常,code为6,对应的是SQLITE_LOCKED,详见:https://www.sqlite.org/rescode.html#busy,具体意思就是说,SQLITE_LOCKED错误码是在同一个数据库连接存在冲突,或者不同的数据库连接共享相同的数据库缓存存在冲突的时候,写操作将无法继续,这里的冲突是什么意思呢?比如,有一个删除表的操作发生在其他的线程在对这个表进行读操作的过程中,那么就会报SQLITE_LOCKED异常,也就是说一个线程的删除表操作和另一个线程对相同表的读取操作存在冲突,前提是这两个操作都是使用同一个数据库连接
  • java.lang.IllegalStateException: get field slot from row 0 col 0 failed异常,这个异常是数据库在执行查询操作的时候,如果数据库中的一条记录所占用的内存大于1MB的话,这时候查询操作就会报错,解决方法就是让每一条的数据库记录的大小都不要超过1MB,这里是单条记录的大小不能超过1MB,如果是每条数据库记录大小都不超过1MB,但是10条加起来超过1MB,那这是没有问题的,此问题在旧版的sqlcipher会出现,但是在新版的sqlcipher貌似已经修复了这个bug,但是只是提高了1MB的阀值,至于怎么提高的?看下面这个issue:
We are glad to hear 3.5.7 is working well for you. We've adjusted the library to allow it to dynamically resize the backing memory for the cursor, so you would be limited by the device.

详见:https://github.com/sqlcipher/android-database-sqlcipher/issues/341#issuecomment-310289295,现在是改成动态来分配大小的,所以限制的上限就会由机器来决定,也就是说,仍然存在这个问题,如果存入数据库的记录太大,还是有可能发生此异常,我们不建议让sqlite数据库中去存储blog这种大的数据记录,应该大的数据记录存成文件,然后把文件路径存到数据库中会更加合适

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏NetCore

Identity Service - 解析微软微服务架构eShopOnContainers(二)

接上一篇,众所周知一个网站的用户登录是非常重要,一站式的登录(SSO)也成了大家讨论的热点。微软在这个Demo中,把登录单独拉了出来,形成了一个Service,...

27550
来自专栏Android干货

Android项目实战(二十五):Android studio 混淆+打包+验证是否成功

36270
来自专栏智能大石头

NewLife.Net——开始网络编程

网络编程的重要性就不说了,先上源码:https://github.com/nnhy/NewLife.Net.Tests

10200
来自专栏逸鹏说道

直传文件到Azure Storage的Blob服务中

题记:为了庆祝获得微信公众号赞赏功能,忙里抽闲分享一下最近工作的一点心得:如何直接从浏览器中上传文件到Azure Storage的Blob服务中。 为什么 如果...

37870
来自专栏张戈的专栏

Haproxy安装部署文档及多配置文件管理方案

最近我在负责一个统一接入层的建设项目,涉及到 Haproxy 和 ospf 的运维部署,本文分享一下我在部署 Haproxy 之后整理的运维部署规范,并实现了H...

918120
来自专栏张善友的专栏

在 Windows 上安装Rabbit MQ 指南

rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统。他遵循Mozilla Public License开源协议。采用 Erlang 实现...

22890
来自专栏逸鹏说道

并发编程~先导篇上

并发 :一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

21380
来自专栏Seebug漏洞平台

使用 XML 内部实体绕过 Chrome 和 IE 的 XSS 过滤器

来源:BypassingXSSFiltersusingXMLInternalEntities 原作者:DavidLitchfield (david@davidl...

416100
来自专栏依乐祝

Net Core集成Exceptionless分布式日志功能以及全局异常过滤

这篇文章有一部分内容翻译自官方文档,[点我阅读][https://github.com/exceptionless/Exceptionless.Net/wiki...

11320
来自专栏从零开始学 Web 前端

linux内核移植过程问题总结

移植内核:2.6.30.4 内核根目录下的.config为当前配置内核的且已经配置好的内核配置。make zImage以此为依据 配置内核的过程: cd lin...

40120

扫码关注云+社区

领取腾讯云代金券