后台线程读取数据库报错求助:FMDatabase正被使用及崩溃问题
嘿,我来帮你捋清楚这个问题——你遇到的EXC_BAD_INSTRUCTION崩溃和那条"FMDatabase is currently in use"日志,本质都是FMDB的FMDatabase实例不支持多线程并发操作导致的。咱们一步步解决:
核心原因
FMDatabase本身不是线程安全的,它内部没有做线程同步处理。如果多个线程(比如后台线程+主线程,或者多个后台线程)同时用同一个FMDatabase实例执行操作,就会出现连接冲突,要么报"正在使用"的错误,要么因为内部状态混乱直接崩溃。
具体解决方案
1. 用FMDatabaseQueue替换单个FMDatabase实例
FMDB官方专门提供了FMDatabaseQueue来处理多线程场景,它内部维护了一个串行队列,所有数据库操作都会排队执行,从根源避免并发冲突。
实现步骤:
- 把你的数据库实例改成
FMDatabaseQueue的单例(全局复用,不要每个VC单独创建):// 比如在你的DB管理类里 + (FMDatabaseQueue *)sharedQueue { static FMDatabaseQueue *queue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *dbPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"yourDB.sqlite"]; queue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; }); return queue; } - 所有数据库操作(查询、更新)都通过
inDatabase或inTransaction方法执行:// 后台线程执行查询示例 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[YourDBManager sharedQueue] inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"SELECT * FROM your_table WHERE id = ?", @(1)]; while ([rs next]) { // 处理查询到的数据 NSString *name = [rs stringForColumn:@"name"]; // ... } [rs close]; // 记得关闭结果集 }]; });
2. 确保结果集和连接被正确关闭
有时候崩溃是因为某个线程的操作没正确关闭FMResultSet或数据库连接,导致连接被长期占用。建议用@try @finally来保证资源释放:
[[YourDBManager sharedQueue] inDatabase:^(FMDatabase *db) { FMResultSet *rs = nil; @try { rs = [db executeQuery:@"SELECT * FROM your_table"]; while ([rs next]) { // 处理数据 } } @finally { [rs close]; // 无论是否出错,都关闭结果集 } }];
3. 清理旧的FMDatabase实例使用方式
之前你怀疑是实例问题,没错——如果多个VC各自持有或复用同一个FMDatabase实例,在多线程下必然冲突。现在统一换成FMDatabaseQueue单例后,所有VC都通过这个单例操作数据库,就不会有问题了。
4. 验证并发场景
如果还有崩溃,可以检查是否有遗漏的旧代码:比如某个VC里还在直接用FMDatabase实例操作,或者在后台线程里没有通过FMDatabaseQueue执行操作。把所有数据库操作都迁移到FMDatabaseQueue的调用方式下,问题就能解决。
总结
本质就是用FMDB提供的线程安全方案FMDatabaseQueue替代非线程安全的FMDatabase实例,规范所有数据库操作的执行方式,就能彻底解决"正在使用"的错误和崩溃问题。
内容的提问来源于stack exchange,提问作者grabarz121

