前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Sqlite并发问题

Android Sqlite并发问题

作者头像
大大大大大先生
发布2018-09-04 15:14:59
1.4K0
发布2018-09-04 15:14:59
举报
文章被收录于专栏:移动开发的那些事儿

背景

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

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

代码语言:javascript
复制
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的方式来实现数据库共享也是不安全的,因为:

代码语言:javascript
复制
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:
代码语言:javascript
复制
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这种大的数据记录,应该大的数据记录存成文件,然后把文件路径存到数据库中会更加合适

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.02.01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
    • 多进程操作同一个数据库文件出现了问题
    • 拓展
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档