在之前的iOS Socket重构设计里面我有提到我们使用了FMDB做消息缓存,在数据库选型方面,我们西兰花也对目前比较流行和成熟的RealmFMDBCore Data做了调查,里面包括了安装、使用和性能比较,是个不错的参考例子

在选型时,我们应该多选取几个作为对比,从使用方面评估学习成本,通过测试不同数据库操作来比较性能差异,了解有哪些大型的App使用了该数据库以及评价来侧面说明该数据库的成熟度和在使用过程中出现的问题,最后根据自己实际的业务需求来选型

在选定使用FMDB之后,我也只是简单的了解下FMDB的使用,并未对内部的实现和设计思路做深入了解,但是在阅读了代码之后,FMDB确实像其他博客里面提到的那样,是对原生的SQLite API进行了包装,暴露出相对友好的对外接口,只需传入SQL语句即可(但是对于习惯于使用Model操作的我们来说,直接写SQL语句还是比较麻烦的,所以GitHub上也就库对FMDB进行了封装,省去写SQL语句,直接对Model进行操作),并且FMDB内部对SQL语句进行了缓存,再配合上多线程并发执行,在提高效率方面做了不少的优化,另外还扩展了内存/文件的IO操作和虚表的操作

下面我会将API使用和源码结合起来讲,方便了解FMDB以及对复习下原生的SQLite API

FMDatabase

初始化

数据库的初始化对外有两种方法+ databaseWithPath:- initWithPath:,内部实际上+ databaseWithPath:只是对- initWithPath:包装,代码里很多类似FMDBReturnAutoreleased等等这一类宏定义是为了兼容ARC和MRC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
+ (instancetype)databaseWithPath:(NSString*)aPath {
return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
}

- (instancetype)init {
return [self initWithPath:nil];
}

- (instancetype)initWithPath:(NSString*)aPath {
// SQLite支持三种线程模式,分别为单线程模式、多线程模式和串行模式
// sqlite3_threadsafe()的返回值可以确定编译时指定的线程模式,其中对于单线程模式,sqlite3_threadsafe()返回false,对于另外两个模式,则返回true。这是因为单线程模式下没有进行互斥,所以多线程下是不安全的
assert(sqlite3_threadsafe()); // whoa there big boy- gotta make sure sqlite it happy with what we're going to do.

self = [super init];

if (self) {
_databasePath = [aPath copy];
_openResultSets = [[NSMutableSet alloc] init];
// 此时并不创建数据库,真正创建是在open的时候
_db = nil;
_logsErrors = YES;
_crashOnErrors = NO;
_maxBusyRetryTimeInterval = 2;
}

return self;
}

使用:

1
2
3
4
// 给定database路径
NSString *dbPath = @"/tmp/tmp.db";
// 调用[+ databaseWithPath:]方法
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];

打开数据连接

打开连接提供了三种方法,这时候才开始创建数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (BOOL)open {
if (_db) {
return YES;
}
// 根据数据库路径打开数据库,如果数据库不存在就新建一个
int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
}
// 设置最大繁忙重试时间间隔,默认为2秒
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}

return YES;
}
1
2
3
4
5
6
7
8
9
// 根据标记打开一个新的数据库连接
// flag可以是以下三种值之一:
// SQLITE_OPEN_READONLY,只读模式,如果数据库不存在会报错
// SQLITE_OPEN_READWRITE,读写模式,如果该文件在操作系统中是写保护的,那就是以只读方式打开,如果数据库不存在会报错
// SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,以读写方式打开,如果数据不存在则新建一个,这个用于open方法里面
// 以上还可以可选组合`SQLITE_OPEN_NOMUTEX`, `SQLITE_OPEN_FULLMUTEX`, `SQLITE_OPEN_SHAREDCACHE`, `SQLITE_OPEN_PRIVATECACHE`, and/or `SQLITE_OPEN_URI`
- (BOOL)openWithFlags:(int)flags {
return [self openWithFlags:flags vfs:nil];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (BOOL)openWithFlags:(int)flags vfs:(NSString *)vfsName {
#if SQLITE_VERSION_NUMBER >= 3005000
if (_db) {
return YES;
}
// 比sqlite3_open多传入了flags和vfsName,vfsName为使用的VFS模块的名称
// sqlite3_open_v2里zVfs参数允许客户应用程序命名一个虚拟文件系统(Virtual File System)模块,用来与数据库连接。VFS作为SQlite library和底层存储系统(如某个文件系统)之间的一个抽象层,通常客户应用程序可以简单的给该参数传递一个NULL指针,以使用默认的VFS模块。
// sqlite3_open_v2比sqlite3_open和sqlite3_open16强大在它可以指定连接方式
int err = sqlite3_open_v2([self sqlitePath], (sqlite3**)&_db, flags, [vfsName UTF8String]);
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
}

if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}

return YES;
#else
NSLog(@"openWithFlags requires SQLite 3.5");
return NO;
#endif
}

open时会根据_maxBusyRetryTimeInterval来设置繁忙处理busy handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout {

_maxBusyRetryTimeInterval = timeout;

if (!_db) {
return;
}
/*
int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
第二个参数是回调函数(busy handler),如果设置了回调函数,那就需要设置sqlite3_busy_handler的第三个参数,这里传递给它的是一个void*的参数的拷贝;如果回调函数返回0时,将不再尝试再次访问数据库而返回SQLITE_BUSY或者SQLITE_IOERR_BLOCKED。如果回调函数返回非0,将会不断尝试操作数据库。
也就是说,程序运行过程中,如果有其他进程或者线程在读写数据库,那么sqlite3_busy_handler会不断调用回调函数,直到其他进程或者线程释放锁。获得锁之后,不会再调用回调函数,从而向下执行,进行数据库操作。该函数是在获取不到锁的时候,以执行回调函数的次数来进行延迟,等待其他进程或者线程操作数据库结束,从而获得锁操作数据库。
*/
if (timeout > 0) {
// busy handler设置为FMDBDatabaseBusyHandler函数
sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self));
}
else {
// turn it off otherwise
// 关闭busy handler
sqlite3_busy_handler(_db, nil, nil);
}
}

// 这就是回调函数
static int FMDBDatabaseBusyHandler(void *f, int count) {
FMDatabase *self = (__bridge FMDatabase*)f;

// 如果是第一次调用,调用[+ timeIntervalSinceReferenceDate]方法,获取当前时间与2001年1月1日00:00:00 UTC的时间间隔,并赋值给startBusyRetryTime
if (count == 0) {
self->_startBusyRetryTime = [NSDate timeIntervalSinceReferenceDate];
// 返回1,则将不断尝试操作数据库
return 1;
}
// 计算当前时间与2001年1月1日00:00:00 UTC的时间间隔和startBusyRetryTime的间隔
NSTimeInterval delta = [NSDate timeIntervalSinceReferenceDate] - (self->_startBusyRetryTime);
// 如果间隔时间小于最大的重试间隔时间
if (delta < [self maxBusyRetryTimeInterval]) {
// 产生一个从50-99的随机整数作为需要挂起毫秒时间
int requestedSleepInMillseconds = (int) arc4random_uniform(50) + 50;
// 调用sqlite3_sleep返回实际挂起毫秒时间,如果不一致,可能是因为SQLite构建时没有将HAVE_USLEEP设置为1
int actualSleepInMilliseconds = sqlite3_sleep(requestedSleepInMillseconds);
if (actualSleepInMilliseconds != requestedSleepInMillseconds) {
NSLog(@"WARNING: Requested sleep of %i milliseconds, but SQLite returned %i. Maybe SQLite wasn't built with HAVE_USLEEP=1?", requestedSleepInMillseconds, actualSleepInMilliseconds);
}
return 1;
}

return 0;
}

使用:

1
2
3
if (![db open]) {
// do somthing
}

执行单个查询SQL语句

主要执行的是(FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args方法,sql是执行SELECT语句,dictionaryArgs是对应于sql语句里keyvalue,在将sql语句转换成prepared语句时,这里先从缓存中获取,没有的话再去调用sqlite3_prepare_v2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
// 检查数据库是否存在
if (![self databaseExists]) {
return 0x00;
}
// 检查是否正在执行操作
if (_isExecutingStatement) {
[self warnInUse];
return 0x00;
}

_isExecutingStatement = YES;

int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *statement = 0x00;
FMResultSet *rs = 0x00;

if (_traceExecution && sql) {
NSLog(@"%@ executeQuery: %@", self, sql);
}
// 将sql语句转换成prepared语句
// 由于使用sqlite3_prepare_v2来生成sql对应的prepared语句代价很大
// 所以使用缓存机制来减少sqlite3_prepare_v2的使用
if (_shouldCacheStatements) {
// 根据sql获取到缓存中的prepared语句
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
// 重置prepared语句
[statement reset];
}
// 如果缓存中没有sql对应的prepared语句,那么需要用sqlite3_prepare_v2生成对应的prepared语句
if (!pStmt) {

rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);

// 如果生成失败,则打印错误日志
if (SQLITE_OK != rc) {
if (_logsErrors) {
NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(@"DB Query: %@", sql);
NSLog(@"DB Path: %@", _databasePath);
}

if (_crashOnErrors) {
NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
// 停止程序
abort();
}
// 释放所有内部资源和FMStatement
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}
}

id obj;
int idx = 0;
// sqlite3_bind_parameter_count 返回SQL语句参数的数量
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)

// If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
if (dictionaryArgs) {

for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {

// Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];

if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
}

// Get the index for the parameter name.
// 通过传入参数名来获取该参数的索引
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);

FMDBRelease(parameterName);
// 如果索引大于0
if (namedIdx > 0) {
// Standard binding from here.
// 在prepared语句里将值绑定到索引位置
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
// increment the binding count, so our check below works out
// 绑定数量加一
idx++;
}
else {
NSLog(@"Could not find index for %@", dictionaryKey);
}
}
}
else {

while (idx < queryCount) {
// 当调用下面三个方法时
// - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
// - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
// - (FMResultSet *)executeQuery:(NSString *)sql values:(NSArray *)values error:(NSError * __autoreleasing *)error
// 值是放在NSArray里面,循环取出来绑定
if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
// 当调用下面两个方法时
// - (FMResultSet *)executeQuery:(NSString*)sql, ...
// - (FMResultSet *)executeQuery:(NSString*)sql withVAList:(va_list)args
// 值是放在va_list里面,循环取出来绑定
else if (args) {
obj = va_arg(args, id);
}
else {
//We ran out of arguments
break;
}

if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
}
else {
NSLog(@"obj: %@", obj);
}
}

idx++;
// 绑定参数
[self bindObject:obj toColumn:idx inStatement:pStmt];
}
}
// 如果绑定数量和参数数量不一致,打印错误,并释放资源
if (idx != queryCount) {
NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}

FMDBRetain(statement); // to balance the release below

// 如果statement不为空,则缓存
if (!statement) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];

if (_shouldCacheStatements && sql) {
// 以sql作为key来缓存statement
[self setCachedStatement:statement forQuery:sql];
}
}

// the statement gets closed in rs's dealloc or [rs close];
// 根据statement和FMDataBase对象来初始化FMResultSet对象
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];

// 将FMResultSet对象加到_openResultSets里
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[_openResultSets addObject:openResultSet];
// useCount+1
[statement setUseCount:[statement useCount] + 1];

FMDBRelease(statement);
// 设置_isExecutingStatement操作结束
_isExecutingStatement = NO;

return rs;
}

使用:

1
2
3
4
5
6
7
rs = [newDb executeQuery:@"select rowid,* from test where a = ?", @"hi'"];

rs = [db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42];

rs = [db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]];

rs = [db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs];

执行单个更新SQL语句

- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args是主要函数,但是里面大部分处理和- executeQuery: withArgumentsInArray: orDictionary: orVAList:处理类似,不同处我已经注释说明,可以直接看注释部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {

if (![self databaseExists]) {
return NO;
}

if (_isExecutingStatement) {
[self warnInUse];
return NO;
}

_isExecutingStatement = YES;

int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *cachedStmt = 0x00;

if (_traceExecution && sql) {
NSLog(@"%@ executeUpdate: %@", self, sql);
}

if (_shouldCacheStatements) {
cachedStmt = [self cachedStatementForQuery:sql];
pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
[cachedStmt reset];
}

if (!pStmt) {
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);

if (SQLITE_OK != rc) {
if (_logsErrors) {
NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(@"DB Query: %@", sql);
NSLog(@"DB Path: %@", _databasePath);
}

if (_crashOnErrors) {
NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
abort();
}

if (outErr) {
*outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
}

sqlite3_finalize(pStmt);

_isExecutingStatement = NO;
return NO;
}
}

id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt);

// If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
if (dictionaryArgs) {

for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {

// Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];

if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
}
// Get the index for the parameter name.
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);

FMDBRelease(parameterName);

if (namedIdx > 0) {
// Standard binding from here.
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];

// increment the binding count, so our check below works out
idx++;
}
else {
// 由于多了outErr,所以绑定时出错需要将error抛出
NSString *message = [NSString stringWithFormat:@"Could not find index for %@", dictionaryKey];

if (_logsErrors) {
NSLog(@"%@", message);
}
if (outErr) {
*outErr = [self errorWithMessage:message];
}
}
}
}
else {

while (idx < queryCount) {

if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
else if (args) {
obj = va_arg(args, id);
}
else {
//We ran out of arguments
break;
}

if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
}
else {
NSLog(@"obj: %@", obj);
}
}

idx++;

[self bindObject:obj toColumn:idx inStatement:pStmt];
}
}


if (idx != queryCount) {
// 同样也是组装error抛出
NSString *message = [NSString stringWithFormat:@"Error: the bind count (%d) is not correct for the # of variables in the query (%d) (%@) (executeUpdate)", idx, queryCount, sql];
if (_logsErrors) {
NSLog(@"%@", message);
}
if (outErr) {
*outErr = [self errorWithMessage:message];
}

sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return NO;
}

/* Call sqlite3_step() to run the virtual machine. Since the SQL being
** executed is not a SELECT statement, we assume no data will be returned.
*/
// sqlite3_prepare函数将SQL命令字符串解析并转换为一系列的命令字节码,这些字节码最终被传送到SQlite3的虚拟数据库引擎(VDBE: Virtual Database Engine)中执行,完成这项工作的是sqlite3_step函数。比如一个SELECT查询操作,sqlite3_step函数的每次调用都会返回结果集中的其中一行,直到再没有有效数据行了。每次调用sqlite3_step函数如果返回SQLITE_ROW,代表获得了有效数据行,可以通过sqlite3_column函数提取某列的值。如果调用sqlite3_step函数返回SQLITE_DONE,则代表prepared语句已经执行到终点了,没有有效数据了。很多命令第一次调用sqlite3_step函数就会返回SQLITE_DONE,因为这些SQL命令不会返回数据。对于INSERT,UPDATE,DELETE命令,会返回它们所修改的行号——一个单行单列的值。
/**
SQLITE_BUSY 数据库文件有锁
SQLITE_LOCKED 数据库中的某张表有锁
SQLITE_DONE sqlite3_step()执行完毕
SQLITE_ROW sqlite3_step()获取到下一行数据
SQLITE_ERROR 一般用于没有特别指定错误码的错误,就是说函数在执行过程中发生了错误,但无法知道错误发生的原因。
SQLITE_MISUSE 没有正确使用SQLite接口,比如一条语句在sqlite3_step函数执行之后,没有被重置之前,再次给其绑定参数,这时bind函数就会返回SQLITE_MISUSE。
**/
rc = sqlite3_step(pStmt);

if (SQLITE_DONE == rc) {
// all is well, let's return.
}
// sql操作被sqlite3_interrupt()函数终止
else if (SQLITE_INTERRUPT == rc) {
if (_logsErrors) {
NSLog(@"Error calling sqlite3_step. Query was interrupted (%d: %s) SQLITE_INTERRUPT", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
else if (rc == SQLITE_ROW) {
NSString *message = [NSString stringWithFormat:@"A executeUpdate is being called with a query string '%@'", sql];
if (_logsErrors) {
NSLog(@"%@", message);
NSLog(@"DB Query: %@", sql);
}
if (outErr) {
*outErr = [self errorWithMessage:message];
}
}
else {
if (outErr) {
*outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
}

if (SQLITE_ERROR == rc) {
if (_logsErrors) {
NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
else if (SQLITE_MISUSE == rc) {
// uh oh.
if (_logsErrors) {
NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
else {
// wtf?
if (_logsErrors) {
NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
}

if (_shouldCacheStatements && !cachedStmt) {
cachedStmt = [[FMStatement alloc] init];

[cachedStmt setStatement:pStmt];

[self setCachedStatement:cachedStmt forQuery:sql];

FMDBRelease(cachedStmt);
}

int closeErrorCode;

if (cachedStmt) {
[cachedStmt setUseCount:[cachedStmt useCount] + 1];
closeErrorCode = sqlite3_reset(pStmt);
}
else {
/* Finalize the virtual machine. This releases all memory and other
** resources allocated by the sqlite3_prepare() call above.
*/
closeErrorCode = sqlite3_finalize(pStmt);
}

if (closeErrorCode != SQLITE_OK) {
if (_logsErrors) {
NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}

_isExecutingStatement = NO;
return (rc == SQLITE_DONE || rc == SQLITE_OK);
}

使用:

1
2
3
4
5
6
7
8
9
10
11
[db executeUpdate:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]];

[dbB executeUpdate:@"create table attached (a text)"];

[dbB executeUpdate:@"insert into attached values (?)", @"test"];

[db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat];

[db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]];

[db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs];

执行多个SQL语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 使用executeStatements:函数可以将多个SQL执行语句写在一个字符串中,并执行
- (BOOL)executeStatements:(NSString *)sql {
return [self executeStatements:sql withResultBlock:nil];
}

- (BOOL)executeStatements:(NSString *)sql withResultBlock:(FMDBExecuteStatementsCallbackBlock)block {

int rc;
char *errmsg = nil;
/*
sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)

该例程提供了一个执行 SQL 命令的快捷方式,SQL 命令由 sql 参数提供,可以由多个 SQL 命令组成。

在这里,第一个参数 sqlite3 是打开的数据库对象,sqlite_callback 是一个回调,data 作为其第一个参数,errmsg 将被返回用来获取程序生成的任何错误。

sqlite3_exec() 程序解析并执行由 sql 参数所给的每个命令,直到字符串结束或者遇到错误为止。
*/
rc = sqlite3_exec([self sqliteHandle], [sql UTF8String], block ? FMDBExecuteBulkSQLCallback : nil, (__bridge void *)(block), &errmsg);

if (errmsg && [self logsErrors]) {
NSLog(@"Error inserting batch: %s", errmsg);
sqlite3_free(errmsg);
}

return (rc == SQLITE_OK);
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSString *sql = @"create table messages1 (id integer primary key messageId, x text);"
"create table messages2 (id integer primary key messageId, y text);"
"insert into messages1 (x) values ('X');"
"insert into messages2 (y) values ('Y');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from messages1;"
"select count(*) as count from messages2;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
// do something
}];

获取最后一条插入数据的RowId

可以根据这个id拿到该数据,并且向上取数据库数据等操作都可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (sqlite_int64)lastInsertRowId {
// 如果有正在执行语句,返回
if (_isExecutingStatement) {
[self warnInUse];
return NO;
}

_isExecutingStatement = YES;
// sqlite3_last_insert_rowid 获取指定数据库最后一个插入的rowid
sqlite_int64 ret = sqlite3_last_insert_rowid(_db);

_isExecutingStatement = NO;

return ret;
}

统计上一次SQL语句受影响的行数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (int)changes {
if (_isExecutingStatement) {
[self warnInUse];
return 0;
}

_isExecutingStatement = YES;

int ret = sqlite3_changes(_db);

_isExecutingStatement = NO;

return ret;
}

exclusive事务和deferred事务

事务可以从DEFERREDIMMEDIATE或者EXCLUSIVE,一个事务的类型在BEGIN命令中指定:BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION

一个deferred事务不获取任何锁,直到它需要锁的时候,而且BEGIN语句本身也不会做什么事情——它开始于UNLOCK状态;默认情况下是这样的。如果仅仅用BEGIN开始一个事务,那么事务就是DEFERRED的,同时它不会获取任何锁,当对数据库进行第一次读操作时,它会获取SHARED LOCK;同样,当进行第一次写操作时,它会获取RESERVED LOCK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 开始延迟事务
- (BOOL)beginDeferredTransaction {

BOOL b = [self executeUpdate:@"begin deferred transaction"];
if (b) {
_inTransaction = YES;
}

return b;
}

//Exclusive事务会试着获取对数据库的EXCLUSIVE锁。这与IMMEDIATE类似,但是一旦成功,EXCLUSIVE事务保证没有其它的连接,所以就可对数据库进行读写操作了。

// 开始事务
- (BOOL)beginTransaction {

BOOL b = [self executeUpdate:@"begin exclusive transaction"];
if (b) {
_inTransaction = YES;
}

return b;
}

// 回滚事务
- (BOOL)rollback {
BOOL b = [self executeUpdate:@"rollback transaction"];

if (b) {
_inTransaction = NO;
}

return b;
}
// 提交事务
- (BOOL)commit {
BOOL b = [self executeUpdate:@"commit transaction"];

if (b) {
_inTransaction = NO;
}

return b;
}

数据库加密及重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (BOOL)setKeyWithData:(NSData *)keyData {
// SQLITE_HAS_CODEC 用来确定是否支持加密
#ifdef SQLITE_HAS_CODEC
if (!keyData) {
return NO;
}

// int sqlite3_key( sqlite3 *db, const void *pKey, int nKey)
// db是指定的数据库,pKey是密钥,nKey是密钥的长度
// 例如:sqlite3_key(_db, "gongcheng", 9);
int rc = sqlite3_key(_db, [keyData bytes], (int)[keyData length]);

return (rc == SQLITE_OK);
#else
#pragma unused(keyData)
return NO;
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (BOOL)rekeyWithData:(NSData *)keyData {
#ifdef SQLITE_HAS_CODEC
if (!keyData) {
return NO;
}

// int sqlite3_rekey( sqlite3 *db, const void *pKey, int nKey)
// db是指定的数据库,pKey是密钥,nKey是密钥的长度
// 例如:sqlite3_rekey(_db, "yzy", 3);
int rc = sqlite3_rekey(_db, [keyData bytes], (int)[keyData length]);

if (rc != SQLITE_OK) {
NSLog(@"error on rekey: %d", rc);
NSLog(@"%@", [self lastErrorMessage]);
}

return (rc == SQLITE_OK);
#else
#pragma unused(keyData)
return NO;
#endif
}

数据库回滚时设置回滚节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (NSError*)inSavePoint:(void (^)(BOOL *rollback))block {
#if SQLITE_VERSION_NUMBER >= 3007000
static unsigned long savePointIdx = 0;
// 设置节点名称
NSString *name = [NSString stringWithFormat:@"dbSavePoint%ld", savePointIdx++];
// 默认不回滚
BOOL shouldRollback = NO;

NSError *err = 0x00;
// 先对当前状态进行保存
if (![self startSavePointWithName:name error:&err]) {
return err;
}

if (block) {
block(&shouldRollback);
}
// 如果需要回滚,则回滚当上一个节点
if (shouldRollback) {
// We need to rollback and release this savepoint to remove it
[self rollbackToSavePointWithName:name error:&err];
}
// 释放节点
[self releaseSavePointWithName:name error:&err];

return err;
#else
NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil);
if (self.logsErrors) NSLog(@"%@", errorMessage);
return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
#endif
}

关闭数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (BOOL)close {
// 清除缓存的prepared语句
[self clearCachedStatements];
// 关闭所有打开的FMResultSet对象
[self closeOpenResultSets];

if (!_db) {
return YES;
}

int rc;
BOOL retry;
BOOL triedFinalizingOpenStatements = NO;

do {
retry = NO;
// 调用sqlite3_close尝试关闭数据库
rc = sqlite3_close(_db);
// 当返回结果是数据库繁忙或者被锁住了
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
if (!triedFinalizingOpenStatements) {
triedFinalizingOpenStatements = YES;
sqlite3_stmt *pStmt;
// 从关联的pDb数据里面对应的prepared语句开始往下找相应的prepared语句,
// 如果pStmt是NULL,则从pDb的第一个prepared语句开始找,
// 如果没有找到,则返回NULL
while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
// 找到之后,释放资源
NSLog(@"Closing leaked statement");
sqlite3_finalize(pStmt);
retry = YES;
}
}
}
else if (SQLITE_OK != rc) {
NSLog(@"error closing!: %d", rc);
}
}
while (retry);

_db = nil;
return YES;
}

使用:

1
[newDb close];