每日更新关注: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. java(MyEclipse)创建webservice和测试webservice

    转载地址:http://blog.csdn.net/hsfy2012/article/details/46300921 创建并发布自己的Webservice的工具  1 安装MyEclipse  2 ...

  2. C语言第二次作业-----顺序结构

    一:改错题 (1)输出指定信息: 将给定源代码输入编译器: 执行编译命令,发现编译器报错,错误信息如下: 经检查,发现源程序将"stdio.h"误拼为"stido.h&q ...

  3. Spring boot结合Maven实现不同环境的配置

    配置文件层次: pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http ...

  4. json字符串转json对象,json对象转换成java对象

    @RequestMapping(value = "updateInvestorApplyAccountNo", method = RequestMethod.POST) @Resp ...

  5. 函数的形参和实参之arguments对象

    当函数调用函数时候传入的实参比函数声明时候制定的形参要少时候,剩余的形参就设置成了undefined.例如 function getPropertyNames(o,/*optional*/a){ va ...

  6. php闭包类外操作私有属性

    Closure::bind() Closure::bindTo(); class person{ private $age; private $sex; public function __const ...

  7. Kali安装使用文泉驿字体

    安装文泉驿字体 Kali选择"简体中文"安装后,在终端等地方发现字体总有重叠,只要安装中文字体即可,这里推荐文泉驿字体.文泉驿是一个以开发开源.免费中文电子资源--如汉字字体.词库 ...

  8. ACM Ignatius and the Princess II

    Problem Description Now our hero finds the door to the BEelzebub feng5166. He opens the door and fin ...

  9. Node.js 多进程

    我们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能. 每个子进程总是带有三个流对象:child.st ...

  10. jQuery 效果 – 动画

    在使用jQuery动画时,你可能想要实现更加丰富的效果,那么你可以通过使用 jQuery animate() 方法自定义动画来达到目的,具体的使用方法如下文所述. jQuery animate() 方 ...