每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

今天终于解决了多线程同时访问数据库时,报数据库锁定的问题,错误信息是:

Unknown error finalizing or resetting statement (5: database is locked)

最后通过FMDatabaseQueue解决了这个问题,本文总结一下:

FMDatabase不能多线程使用同一个实例

多线程访问数据库,不能使用同一个FMDatabase的实例,否则会发生异常。如果线程使用单独的FMDatabase实例是允许的,但是同样有可能发生database is locked的问题。这是由于多线程对sqlite的竞争引起的

我的app一开始就是多线程使用单独的FMDatabase实例访问数据库,虽然没有引起crash,但是还是出现了database is locked问题,造成很多数据没有如预期写入数据库

使用FMDatabaseQueue,问题依旧

后来上FMDB的官网看了文档,确认用FMDatabaseQueue可以解决这个问题,API也比较简单:

NSString *dbFilePath = [PathResolver databaseFilePath];
queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
[queue inDatabase:^(FMDatabase *db){
    // access db
}];

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

但是实际测试了一下,还是database is locked

读了一下相关的源码,FMDatabaseQueue解决这个问题的思路是:创建一个队列,然后将放入队列的block顺序执行,这样避免了多线程同时访问数据库

而我的代码是多线程各创建FMDatabaseQueue的实例,所以其实有多个队列,因此还是存在数据库竞争的问题,和用FMDatabase时是一样的

共享同一个FMDatabaseQueue实例

于是接下来我让每个线程使用同一个Queue实例,问题就顺利解决了

实现的方式,一开始我想给FMDatabase增加一个单例方法,但是这样以后升级FMDB会比较麻烦,所以最后我是创建了一个Helper类

@implementation LosDatabaseHelper

{
    FMDatabaseQueue* queue;
}

-(id) init
{
    self = [super init];
    if(self){
        NSString *dbFilePath = [PathResolver databaseFilePath];
        queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
    }
    return self;
}

+(LosDatabaseHelper*) sharedInstance
{
    static dispatch_once_t pred = 0;
    __strong static id _sharedObject = nil;
    dispatch_once(&pred, ^{
        _sharedObject = [[self alloc] init];
    });
    return _sharedObject;
}

-(void) inDatabase:(void(^)(FMDatabase*))block
{
    [queue inDatabase:^(FMDatabase *db){
        block(db);
    }];
}

@end

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

系统中其他的类,使用这个Helper类的单例,这样保证了全局只有唯一的FMDatabaseQueue实例。注意,因为Helper内部持有的是 FMDatabaseQueue,所以可以这么做,如果包装的是FMDatabase类,就绝对会有问题。因为FMDatabase实例不能在多线程环境 共享

使用FMDatabaseQueue之后,管理db

原本使用FMDatabase类,需要手工调用db的open和close方法

但是用FMDatabaseQueue,不需要调用open,因为查看代码发现,Queue已经open了。至于要不要close,我也不确定,因 为官方的sample code没有调用close。实际应用中,我也没有调用,好像没有问题。如果需要close的话,我想可以在Helper类的公共方法里增加调用 close queue就可以了。下面是close的源码:

- (void)close {
    FMDBRetain(self);
    dispatch_sync(_queue, ^() {
        [_db close];
        FMDBRelease(_db);
        _db = 0x00;
   });
   FMDBRelease(self);
}
所以,使用Queue,是不需要自己打开和关闭db的。但是如果使用了FMResultSet,rs倒是需要关闭,否则会报warning:
if ([db hasOpenResultSets]) {
    NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
为了不看到warning,我都在block里调用了[rs close]

刷新数据库文件路径

具体到我们的应用,还有一个特殊问题需要考虑。因为我们的APP可以切换账户,而账户的db文件是独立的。所以当用户重新登录的时候,需要刷新一下Helper的queue

+(void) refreshDatabaseFile
{
    LosDatabaseHelper *instance = [self sharedInstance];
    [instance doRefresh];
}

-(void) doRefresh
{
    NSString *dbFilePath = [PathResolver databaseFilePath];
    queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
}
如果不这么做,由于Helper是单例,那么切换账户以后,用户B访问的还是用户A的数据库。刷新的调用,一般放在登录之后,进入主页面之前就可以了

队列和线程

在debug过程中,顺便看到一个现象。虽然多个block都是放到同一个队列里,但是其实是跑在不同的thread里


不要混淆队列和线程的概念,使用GCD时,开发者关注的是把block放到队列中,但是同一个队列其实可以对应多个thread,为block分配thread,是GCD框架负责的,开发者不需要关注。只要把操作放到合适的队列里,GCD就会完成线程的创建,分配与回收

每日更新关注:http://weibo.com/hanjunqiang 
新浪微博

使用FMDB多线程访问数据库,及database is locked的问题的更多相关文章

  1. IOS 使用FMDB多线程访问数据库 及databaseislocked的问题

    原理:文件数据库sqlite,同一时刻允许多个进程/线程读,但同一时刻只允许一个线程写.在操行写操作时,数据库文件被琐定,此时任何其他读/写操作都被阻塞,如果阻塞超过5秒钟(默认是5秒,能过重新编译s ...

  2. (原创)android Sqlite多线程访问异常解决方案

    在开发Android的程序的时候sqlite数据库是经常用到的:在多线程访问数据库的时候会出现这样的异常:java.lang.IllegalStateException: Cannot perform ...

  3. 使用FMDB多线程訪问数据库,及database is locked的问题

    今天最终攻克了多线程同一时候訪问数据库时,报数据库锁定的问题.错误信息是: Unknown error finalizing or resetting statement (5: database i ...

  4. [R语言]R语言使用多线程对数据库进行大批量访问时出现无法连接问题

    问题描述: 在R中使用多线程对数据库进行写入,在服务器端运行脚本(linux环境),总是在第6-7万个任务线程时,出现无法连接到数据库的问题.任务中断,错误信息为task 6xxxx failed,C ...

  5. 【翻译】Android多线程下安全访问数据库

    为了记录如何线程安全地访问你的Android数据库实例,我写下了这篇小小札记.文章中引用的项目代码请点击这里       假设你已编写了自己的 SQLiteOpenHelper. publicclas ...

  6. sqlite:多线程操作数据库“database is locked”解决方法(二)

    上一篇博客<sqlite:多线程操作数据库“database is locked”解决方法>通过注册延时函数的方法来处理数据库被锁的问题.此方法固然能解决问题,但是在多个线程向数据库写入大 ...

  7. 在Golang中如何正确地使用database/sql包访问数据库

    本文记录了我在实际工作中关于数据库操作上一些小经验,也是新手入门golang时我认为一定会碰到问题,没有什么高大上的东西,所以希望能抛砖引玉,也算是对这个问题的一次总结. 其实我也是一个新手,机缘巧合 ...

  8. Android 异步任务,通过PHP访问数据库,多线程,线程间通讯

    文章列表MainActivity.java package com.eric.asynctask; import java.io.IOException; import java.util.Array ...

  9. CoreData和SQLite多线程访问时的线程安全

    关于CoreData和SQLite多线程访问时的线程安全问题 数据库读取操作一般都是多线程访问的.在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱.IOS中常 ...

随机推荐

  1. centos7安装nginx必要环境

    安装所需环境 Nginx 是 C语言 开发,建议在 Linux 上运行 , 在安装nginx前还要安装以下的环境包 一. gcc 安装安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc ...

  2. Android自定义模糊匹配搜索控件(二)

    在项目中遇到一个需要通过某个字的值筛选匹配带出其他信息的需求,在这里将实现思路整理出来. 源码地址:https://github.com/whieenz/SearchSelect 先看效果图 上图中的 ...

  3. Java并发编程之并发工具类

    CountDownLatch CountDownLatch可以用于一个或多个线程等待其他线程完成操作. 示例代码 private static CountDownLatch c = new Count ...

  4. 用 ConfigMap 管理配置 - 每天5分钟玩转 Docker 容器技术(159)

    Secret 可以为 Pod 提供密码.Token.私钥等敏感数据:对于一些非敏感数据,比如应用的配置信息,则可以用 ConfigMap. ConfigMap 的创建和使用方式与 Secret 非常类 ...

  5. JAVA虚拟机:对象的创建过程

    简要说明的话,Java对象的创建过程分为下面几步: 1.执行相关检查: 2.为对象分配内存,将分配到的内存空间都初始化为零值: 3.进行构造代码块和构造函数的初始化 下面详细介绍这几个步骤: 1.执行 ...

  6. SVN错误:SVN Working copy XXX is too old

    出错原因: 这是因为使用了低版本的svn生成了.svn内文件内容,但是,使用高版本svn同步时便出现该问题. 解决方法: 找到报错对应的文件夹,里面有个名为.svn的文件夹,删除这个文件夹(这是svn ...

  7. linux上快速搭建宝塔面板来操作便捷功能

    关于宝塔 Linux版请确保纯净系统安装(支持CentOS.Ubuntu.Debian.Fedora.Deepin),Web端管理,QQ群及论坛技术支持一键LAMP/LNMP.创建网站FTP.数据库. ...

  8. Http多线程版本

    上一篇文章讲了HTTP是如何通过TCP协议传输到服务器上,以及服务器接收到的报文信息请参考[HTTP与TCP的关系] 这篇文章主要讲述的多线程处理Http请求,关于多线程的好处我就不再叙述了.由于我们 ...

  9. Git-gitblit-Tortoisegit 搭建Windows Git本地服务器

    1.Gitblit安装 1.1.Gitblit简介 Git在版本控制领域可谓是深受程序员喜爱.对于开源的项目,可以免费托管到GitHub上面,相当的方便.但是私有项目托管到GitHub会收取相当昂贵的 ...

  10. Go 语言切片(Slice)

    Go 语言切片是对数组的抽象. Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固 ...