首先数据库是系统资源,就像我们操作文件一样,所以并发操作时要注意安全
在iOS上,只有一个线程能够打开数据库操作,其他线程要操作数据库必须等数据库关闭后才能打开操作。
多线程时:每个线程独立打开数据库,操作数据库,操作完后关闭数据库。打开和关闭都比较费时间,而且要手动控制打开关闭锁,在每个线程操作不频率时可用该方法。
如果多个线程频繁操作数据库,使用以上方法很容易造成系统崩溃,解决方案:开启第3种串行模式,使用一个类(单例方式)操作数据库。
配置
sqlite3_config(SQLITE_CONFIG_SERIALIZED);为串行模式
以上是使用sqlite3 API时所使用的一些思路
很多iOS项目中都使用FMDB这个第三方开源库,但FMDB不能在多个线程中共同一个FMDatabase对象,因为这个类本身不是线程安全的,如果这样使用会造成数据混乱等问题。
如要需要多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了。
首先用一个数据库文件地址来初使化FMDatabaseQueue,然后就可以将一个闭包(block)传入inDatabase方法中。 在闭包中操作数据库,而不直接参与FMDatabase的管理。
FMDatabaseQueue解决这个问题的思路是:创建一个队列,然后将放入队列的block顺序执行,这样避免了多线程同时访问数据库;
如果是多线程各创建FMDatabaseQueue的实例,其实有多个队列,还是存在数据库竞争的问题,和用FMDatabase时是一样的;
让每个线程使用同一个Queue实例,问题就顺利解决了;
当然根据FMDB源码中给出的思路我们可以自己实现多线程访问数据库时候的安全问题,以下供参考
+ (instancetype)sharedInstance
{
static SyncObj* instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [SyncObj new];
});
return instance;
}
-(void) safeTest
{
io_sync_safe(^{
NSLog(@"safe print -- func safeTest()");
});
}
static const void * const NTESDispatchIOSpecificKey = &NTESDispatchIOSpecificKey;
dispatch_queue_t NTESDispatchIOQueue()
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("db.queue", 0);
dispatch_queue_set_specific(queue, NTESDispatchIOSpecificKey, (void *)NTESDispatchIOSpecificKey, NULL);
});
return queue;
}
typedef void(^dispatch_block)(void);
void io_sync_safe(dispatch_block block)
{
if (dispatch_get_specific(NTESDispatchIOSpecificKey))
{
NSLog(@"3. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
block();
}
else
{
dispatch_sync(NTESDispatchIOQueue(), ^() {
NSLog(@"4. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
block();
});
}
}
一般数据库打开关闭都比较耗时所以都是自己创建一个单例类打开数据库,所有的操作就封装该类的方法中