iOS数据库的使用(三):sqlite3在FMDB中的实现
sqlite3中的三种线程模式
Single-thread(单线程):值为0,所有的互斥所都被禁止。这种模式在极度要求速度的情况下被建议使用。因为没有加锁,所以在多线程中使用时是不安全的。
Multi-thread(多线程):值为2,在部分地方加锁,部分地方禁止了互斥所。可以在多线程是使用多个连接,但是一个连接同时被多个线程使用时,是不安全的。
Serialized(串行):值为1,所有的互斥所都被开启。这种模式无论是多个连接在多线程中使用,还是单个连接在多线程中使用,最终都被被强制成串行执行,所以是绝对线程安全的,但是速度最慢的。
官方文档:SQLITE_THREADSAFE=<0 or 1 or 2>
sqlite3中三种模式是如何实现的
要了解这个问题就要先了解互斥锁加在了哪些地方?
Multi-thread:如官方文档中锁描述的,其本质是不要在多线程中同时使用同一个database connection
或者prepared statements
sqlite线程模式的设置
编译阶段:通过使用编译指令配置相关的参数。例如iOS中的sqlite3.lib就是被编译之后的库,这个lib中sqlite3的线程模式被配置成了2,也就是串行模式。具体的指令如下:
gcc -DSQLITE_THREADSAFE=0 shell.c sqlite3.c -ldl
其意义是:编译时设置SQLITE_THREADSAFE参数的值为0,编译shell.c和sqlite3.c,生成命令行执行程序。
初始化阶段:在调用sqlite3_initialize()之前使用sqlite3_config()函数设置。可以认为是在调用open方法之前
运行时:通过sqlite3_open_v2()中的第三个参数来设置,可选值为SQLITE_OPEN_NOMUTEX(无锁即多线程模式),SQLITE_OPEN_FULLMUTEX(全锁即串行模式)
sqlite3_config方法
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */
注意:这里的线程模式的值是1、2、3,而编译阶段设置的线程模式的值为0、1、2
各个值得意义:官方文档
Muti-thread模式下的并发
根据官方文档,只要保证了多线程中不同时使用同一个connect即可,所以path使用同一个,也就意味着使用同一个数据库,但是并发中取创建新的db,也就是open的是不同的db,也就是和同一个数据库建立了多个不同的连接,代码如下
主要并发逻辑:
- (void)mutiThreadTest {
// iOS中的sqlite3lib默认是2,也就是muti-thread。也就是说应用程序需要自己去保证不再多线程中同时使用同一个数据库连接(database-connection)。也就是说,可以通过建立多个数据库连接来实现并行访问sqlite
dispatch_queue_t t = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
dispatch_async(t, ^{
FMDatabase *db1 = [FMDatabase databaseWithPath:self.path];
[self addDataFrom:0 count:1000 withDB:db1 withFlag:@"1"];
});
dispatch_async(t, ^{
FMDatabase *db2 = [FMDatabase databaseWithPath:self.path];
[self addDataFrom:1000 count:1000 withDB:db2 withFlag:@"2"];
});
dispatch_async(t, ^{
FMDatabase *db3 = [FMDatabase databaseWithPath:self.path];
[self addDataFrom:2000 count:1000 withDB:db3 withFlag:@"3"];
});
dispatch_async(t, ^{
FMDatabase *db4 = [FMDatabase databaseWithPath:self.path];
[self addDataFrom:3000 count:1000 withDB:db4 withFlag:@"4"];
});
}
viewDidLoad方法:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self create];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:@"查询" forState:UIControlStateNormal];
btn.backgroundColor = [UIColor redColor];
[btn addTarget:self action:@selector(mutiThreadTest) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
btn.frame = CGRectMake(100,100, 40, 40);
}
创建数据库方法:
- (void)create {
self.path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.db"];
self.db = [FMDatabase databaseWithPath:self.path];
self.queue = [FMDatabaseQueue databaseQueueWithPath:self.path];
if ([self.db open]) {
[self.db executeUpdate:@"CREATE table if not exists ClientTable (name text, no text, signature text,PRIMARY KEY(no));"];
[self.db executeUpdate:@"delete from ClientTable"];
}
NSLog(@"%@",self.path);
}
结果:
1、step方法报错
2、可以并行