FMDB可能是iOS中最常用的数据库第三方框架. 在项目中, 对于简单的数据, 我们一般是储存在偏好设置中, 或者存入XML文件, 需要的时候再读取. 但是对于大量的数据, 或者是需要后续频繁操作的数据, 我们只能把这些数据存入数据库中.
在最近的一个项目中, 就遇到了需要频繁操作数据库内数据的情况, 频繁的增删读写.随之而来的就遇到了数据库锁死问题. FMDB报错为database locked.
首先先弄懂为什么会出现数据库锁死问题. 当A在写入数据的时候, 会先锁定数据库再进行写入操作. 然而这时, B又马上想写入数据, 因为数据库已经被A锁住了, 所以B无法进行写入操作, 只能先等待A操作完成后再打开数据库. 当等待的时间过长的时候, 数据库就会报database locked错误. 这个时间好像是2s.
这里先贴出源码中的一句话 :
Using a single instance of
<FMDatabase>
from multiple threads at once is a bad idea. It has always been OK to make a<FMDatabase>
object per thread. Just don't share a single instance across threads, and definitely not across multiple threads at the same time.
告诉我们别妄想着把database做成单例, 也别想着使用多线程访问database对象. 那我们应该怎么处理数据库锁死问题呢? 官方已经给出了答案:
Instead, use
FMDatabaseQueue
.
然后, 楼主参考了网络各路大神的意见, 一致的解决版本是建立一个DatabaseHelper的类, 用来管理数据库, 应该说是管理数据库线程. 即是把FMDatabaseQueue做成一个单例, 所有的的数据库操作在这个线程中串行执行. 当需要执行一个操作时, 先把操作放入串行队列, 执行完前一个操作再执行下一个操作.
上代码:
#import "BPBDBHelper.h"
@implementation BPBDBHelper
{
FMDatabaseQueue* queue;
}
- (id)init {
self = [super init];
if(self){
NSString *dbFilePath = [self getDatabasePath];
queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
}
return self;
}
+ (BPBDBHelper *)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){
if ([db open]) {
block(db);
}
[db close];
}];
}
- (BOOL)creatDatabase {
[queue inDatabase:^(FMDatabase *db) {
//创建表
if ([db open]) {
NSString *positionSql = @"CREATE TABLE IF NOT EXISTS 'position' (******)";
[db executeUpdate:positionSql];
}
[db close];
}];
return true;
}
- (NSString *)getDatabasePath {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"position.sqlite"];
return filePath;
}
+ (void)refreshDatabaseFile {
BPBDBHelper *instance = [self sharedInstance];
[instance doRefresh];
}
- (void)doRefresh {
NSString *dbFilePath = [self getDatabasePath];
queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
}
@end
之后, 只需要把对数据库的各种操作都放入- (void)inDatabase:(void(^)(FMDatabase*))block
内执行就行了.
由于笔者知识有限,如有错误,欢迎讨论指出。
网友评论