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

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

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
}];

但是实际测试了一下,还是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

系统中其他的类,使用这个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就会完成线程的创建,分配与回收

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

时间: 2024-10-03 21:33:34

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

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

原理:文件数据库sqlite,同一时刻允许多个进程/线程读,但同一时刻只允许一个线程写.在操行写操作时,数据库文件被琐定,此时任何其他读/写操作都被阻塞,如果阻塞超过5秒钟(默认是5秒,能过重新编译sqlite可以修改超时时间),就报"database is locked"错误. 所以,在操作sqlite时,应该即时关闭连接:打开连接后,尽量减少非常费时的操作. 多线程同时访问数据库时,报数据库锁定的问题,错误信息是: Unknown error finalizing or reset

Android多线程下安全访问数据库(Concurrent Database Access)

Assuming you have your own SQLiteOpenHelper: 1 public class DatabaseHelper extends SQLiteOpenHelper { ... } Now you want to write data to database in separate threads: 1 // Thread 1 2 Context context = getApplicationContext(); 3 DatabaseHelper helper

[原创]使用FMDB的访问数据库

最近开始写博客了,把我学习到的东西进行汇总和总结. 今天复习到FMDB,FMDB是一个第三方的访问数据库的插件.有了它以后,妈妈再也不用担心我访问数据库的问题. 好了,言归正传,为什么要使用FMDB呢? 最直接的一个原因就是,iOS中的sqlite3访问数据库的代码太过繁琐,难记,写起来也不顺手,究其原因非常简单,就是sqlite3是C封装的一个访问数据库的api,它是用C写的,你想用也要用C的形式去访问,所以,使用起来你会觉得有点变扭,就好像是一个生活在广东的外地人,虽然也可以和广东人交流,但

qt 多线程访问数据库问题

Qt数据库由QSqlDatabase::addDatabase()生成的QSqlDatabase只能在创建它的线程中使用, 在多线程中共用连接或者在另外一个线程中创建query都是不支持的 假设有如下代码: bool openDatabase() { QSqlDatabase db; QString connectionName = "sqlite"; db = QSqlDatabase::addDatabase("QSQLITE", connectionName)

转发 FMDB多线程下"is currently in use" 或者 "database is locked" 问题

FMDB多线程下"is currently in use" 或者 "database is locked" 问题 问题一: "is currently in use" 出现的场景是这样的,多线程操作数据库,每个线程都使用了FMDatabase实例(注意没有使用FMDatabaseQueue). 问题二:“database is locked"出现的场景是这样的,多线程操作数据库,每个线程各自创建了FMDatabaseQueue实例操作数

FMDB多线程下"is currently in use" 或者 "database is locked" 问题

问题一: "is currently in use" 出现的场景是这样的,多线程操作数据库,每个线程都使用了FMDatabase实例(注意没有使用FMDatabaseQueue). 问题二:“database is locked"出现的场景是这样的,多线程操作数据库,每个线程各自创建了FMDatabaseQueue实例操作数据库,或者一个线程创建FMDatabaseQueue实例来操作,而另外的线程创建了FMDatabase实例来操作. 解决:FMDB多线程操作数据库,必须使

android Sqlite多线程访问异常解决方案

在开发Android的程序的时候sqlite数据库是经常用到的:在多线程访问数据库的时候会出现这样的异常:java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.或 java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteData

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

上一篇博客<sqlite:多线程操作数据库“database is locked”解决方法>通过注册延时函数的方法来处理数据库被锁的问题.此方法固然能解决问题,但是在多个线程向数据库写入大量数据的情况下,延时会拖慢进度. 想出方法二: 1. 创建一个链表,链接如下格式的结构体,线程1,线程2,线程3......不直接改写数据库,而是把sql语句插入链表中: typedef struct { uint8_t *buf; uint32_t len; } sqlItem_t; 2. 创建一个独立的线

关于CoreData和SQLite多线程访问时的线程安全问题

http://www.jianshu.com/p/95db3fc4deb3 关于CoreData和SQLite多线程访问时的线程安全问题 数据库读取操作一般都是多线程访问的.在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱.IOS中常用的两种数据持久化存储方式:CoreData和SQLite,两者都需要设置线程安全,在这里以FMDB来解释对SQLite的线程安全访问. 一:FMDB的线程安全:(以读取图片为例) 1.没有线程安全的执行方式: //****