Sqlite3的基本使用主要是对2个对象(sqlite3、pstmt)和8个方法(open、step、close等)的理解,本教程会以此为提纲作深入的讲解,全文以sqlite3的使用流程作为行文顺序并介绍其相关的一些方法的使用,have fun!
1. sqlite对象
每一个打开的数据库都使用一个不透明的sqlite对象来表示。其声明非常简单任性,只是使用typedef起了一个别名,如下:
typedef struct sqlite3 sqlite3;
2. sqlite3_initialize( )
对应的有4个方法:
int sqlite3_initialize(void);
int sqlite3_shutdown(void);
int sqlite3_os_init(void);// 可以忽略,initialize方法中已经根据不同的平台做了实现
int sqlite3_os_end(void);// 可以忽略,shutdown方法中已经根据不同的平台做了实现
initialize 方法和 shutdown 方法对应,os_init 和 os_end 对应,在 os 系统中使用,其意义如下:
- 构造方法和销毁方法一一对应且只能对一个对象调用一次,调用多次不会做额外操作且无伤害(harmless)
- 构造方法需要在使用任何其他的 sqlite api 之前调用(sqlite3_config()方法需要在init之前调用)
- 销毁方法需要在所有连接已经关闭且所有资源被释放的情况下调用
- init方法线程安全,销毁方法线程不安全,销毁方法只能在一个线程中调用
- 编译阶段可以设置自动调用初始化方法,但是官方建议手动调用初始化方法,因为以后的发布版本中会逐步使用非自动初始化的模式。许多方法在内部会自动调用初始化方法,比如 open 方法。如果是非自动初始化的模式,则需要自己手动调用。
- 不能主动调用 os_init 和 os_end,这两个函数已经被封装在了 initialize 方法中,会根据平台的不同进行不同的操作
总结:这个方法一般被封装在了 open 当中,一般不需要主动调用(至少在iOS中使用 sqlite3lib 库时),是一个不需要重点关注的方法
initialize方法官方文档
3. sqlite3_open
建立数据库连接的方法,open 方法应该有三个:
- sqlite3_open
- sqlite3_open16
- sqlite3_open_v2。
其中前两个方法内部调用的就是 open_v2 方法,只是配置的参数不一样,其区别如下:
方法名 | 编码方式 | 第三个参数 | 第四个参数 |
---|---|---|---|
sqlite3_open | UTF-8 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | NULL对象(默认的sqlite3_vfs对象) |
sqlite3_open16 | UTF-16 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | NULL对象(默认的sqlite3_vfs对象) |
sqlite3_open_v2 | UTF-8 | 可配置 | 可配置 |
所以,重点关注 sqlite3_open_v2 方法即可;
两个注意点:
open 方法在建立连接时,无论成功还是失败,都会返回一个指针对象,唯一的例外是当内存不够分配给一个 sqlite3 对象时,open方法会返回一个NULL。所以,每次 open 最好判断是否成功,如果失败则主动 close 掉连接;
sqlite3_open_v2 会返回一个 result,以此来判断数据库连接是否建立成功;
对于 open_v2 第三个参数的配置要求为:必选+可选。
必选为下列三个中的一个:
- SQLITE_OPEN_READONLY(只读);
- SQLITE_OPEN_READWRITE(可读写);
- SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE(可读写,没有就创建)
可选项为下表中的任意配置,相关的官方文档
4. sqlite3_open_v2
sqlite3_open_v2方法需要重点关注,其声明如下:
SQLITE_API int sqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
);
参数的意义:
第一个参数:fineName,就是要连接的数据库的文件名地址
第二个参数:sqlite3 对象,sqlite3 中使用 sqlite3 对象来表示一个连接;
第三个参数:flags,打开数据库时配置的一些参数。下文会具体介绍。
第四个参数:vfs
open_v2可配置的参数:
参数名 | 值 | 使用限制 | 意义 |
---|---|---|---|
SQLITE_OPEN_READONLY | 0x00000001 | /* Ok for sqlite3_open_v2() */ | 只读 |
SQLITE_OPEN_READWRITE | 0x00000002 | /* Ok for sqlite3_open_v2() */ | 可读可写 |
SQLITE_OPEN_CREATE | 0x00000004 | /* Ok for sqlite3_open_v2() */ | 没有则创建 |
SQLITE_OPEN_URI | 0x00000040 | /* Ok for sqlite3_open_v2() */ | 使用URI模式 |
SQLITE_OPEN_MEMORY | 0x00000080 | /* Ok for sqlite3_open_v2() */ | 内存存储 |
SQLITE_OPEN_NOMUTEX | 0x00008000 | /* Ok for sqlite3_open_v2() */ | 并发模式 |
SQLITE_OPEN_FULLMUTEX | 0x00010000 | /* Ok for sqlite3_open_v2() */ | 串行模式 |
SQLITE_OPEN_SHAREDCACHE | 0x00020000 | /* Ok for sqlite3_open_v2() */ | 共享缓存模式 |
SQLITE_OPEN_PRIVATECACHE | 0x00040000 | /* Ok for sqlite3_open_v2() */ | 私有缓存模式 |
总表:Flags For File Open Operations
详细说说第三个参数:
- 如果第三个参数中设置了 SQLITE_OPEN_URI,则第一个参数,也就是 filename 就会采用URI而不是单纯的文件路劲,具体可参考本文后面的URI部分;
- 第三个参数读写模式是必选项,有三个选项:SQLITE_OPEN_READONLY(只读)、SQLITE_OPEN_READWRITE(可写可读)、SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE(可写可读,没有数据库文件则主动创建)。第三个参数必须是这三个选项中的一个,然后再和上表中的其他的参数进行组合。
- mutex 即锁,影响 sqlite 的线程模式;
- cache 选项是设置共享缓存,可参考sqlite系列文章第三篇:https://www.jianshu.com/p/f036a947853b
5. sqlite3_prepare_v2
sqlite3中的 prepare 方法的意义: sql 的执行需要一个字节码程序。而 prepare 方法就是将 sql 语句生成一个二进制的程序等待被执行。
另一个角度而言,prepare 方法是生成一个 prepared statement 对象,而这个对象就代表一个已经被编译成二进制格式的程序。
prepare方法:prepare方法众多,其中 sqlite3_prepare 是历史遗留方法,不能使用,一般使用 sqlite3_prepare_v2 方法,其他方法可以参考官方文档
参数的意义:第一第二个参数不做解释。第三个参数为负数时,从第一个字符开始读取到第一个终止符(也就是'\0'),所以为负数时就是指读取 sql 参数中的所有。为正数时指从第几个 char 字符开始读取,为 0 时不读取。pzTail 指向被解析的字符串的末尾,一般为 null,具体意义可参考官方文档;
ppStmt:指向一个已经编译过的 prepared statement,用于给将要执行的 sql 添加参数
以插入的 sql 为例,具体使用如下:
- (void)sqliteInsert {
sqlite3 *sqlite;
int result = sqlite3_open_v2(DATABASEPATH, &sqlite, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if(result != SQLITE_OK) {
NSLog(@"error opening!: %d", result);
} else {
// 无论是否报错,database connection(也就是sqlite3对象)都会被分配内存,需要close来释放掉内存
sqlite3_close(sqlite);
}
// 使用prepare插入
NSString *insertSql = @"replace INTO ClientTable (name, no, signature) values ( ?, ?, ?);";
sqlite3_stmt *statment;
// prepared方法的作用是使用sql生成一个可执行的程序
sqlite3_prepare_v2(sqlite, [insertSql UTF8String], -1, &statment, NULL);
// 绑定句柄
sqlite3_bind_text(statment, 1,[@"测试" UTF8String], -1, NULL);
sqlite3_bind_text(statment, 2,[@"01" UTF8String], -1, NULL);
sqlite3_bind_text(statment, 3,[@"测试签名" UTF8String], -1, NULL);
// step是执行sql语句
int errorNo = sqlite3_step(statment);
if (errorNo != SQLITE_DONE) {
NSLog(@"错误");
}
sqlite3_finalize(statment);
sqlite3_close(sqlite);
}
6.sqlite3_bind()
bind 方法据绑定参数的类型分为主要的三种,
- sqlite3_bind _text表示字符串;
- sqlite3_bind _blob 表示二进制文件,比如图片、音频;
- sqlite3_bind _double/int/int64等都表示数字。
当然还有其他的,比如null,这里不做细说。因为 oc 中最长使用的是字符串,所以需要重点关注 sqlite3_bind_text();
sqlite3_bind_text的参数:
- 第一个参数:从 prepare 函数中分配好内存的 statement 对象
- 第二个参数:所绑定参数在表中对应的索引
- 第三个参数:绑定的值
- 第四个参数:第三个参数需要取到的结尾的偏移量。如果是text的话,传负数代表取到第一个/0终止符,即取所有字符串。可以为正数,代表只取部分,但是如果取到的字符串中有nul,则会出现不可预知的问题。如果是blob类型,该值不能为负数。
第五个参数:对于blob和text而言,这个参数是处理完成后的回调函数
bind 函数的使用:
- (BOOL)insertWithName:(NSString *)name no:(NSString *)no signature:(NSString *)signature {
sqlite3 *sqlite;
int openFlag = sqlite3_open_v2(DATABASEPATH, &sqlite, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE, NULL);
if (openFlag != SQLITE_OK) {
NSLog(@"数据库打开失败!");
sqlite3_close(sqlite);
}
NSString *sql = @"replace into ClientTable (name,no,signature) values (?, ?, ?);";
sqlite3_stmt *statement;
int prepareFlag = sqlite3_prepare_v2(sqlite, [sql UTF8String], -1, &statement, NULL);
if (prepareFlag !=SQLITE_OK) {
NSLog(@"数据库prepare失败");
}
sqlite3_bind_text(statement, 1, [name UTF8String], -1, NULL);
sqlite3_bind_text(statement, 2, [no UTF8String], -1, NULL);
sqlite3_bind_text(statement, 3, [signature UTF8String], -1, NULL);
// // 获取参数的index,第二个参数怎么传??暂未找到方法
// int index = sqlite3_bind_parameter_index(statement, "name");
int stepFlag = sqlite3_step(statement);
if (stepFlag != SQLITE_DONE) {
NSLog(@"插入执行失败!");
}
sqlite3_finalize(statement);
sqlite3_close(sqlite);
if (stepFlag != SQLITE_DONE) {
return NO;
} else {
return YES;
}
}
7. sqlite3_step()
当 sql 被 prepare、bind 完成后,相当于 sql 语句被变异成了一个可执行程序且给这个程序的执行准备好了参数。
所以,接下来的步骤就是调用 sqlite3_step() 方法来执行 sql 语句。执行完 step 方法后,有三种结果:
报错
错误号包括busy、misuse等,根据错误号进行处理即可SQLITE_ROW
执行完成一次但是还有结果需要返回,需要继续调用step方法获取后续的结果SQLITE_DONE
表示执行完成并已经返回了所有结果
几个注意点
- 首先出现SQLITE_ROW的时,是有数据返回的情况,也就是执行SELECT语句时。
- 每执行一次step()方法返回一行数据
- 必须执行到SQLITE_DONE时才表示所有结果都已经返回,且SQLITE_DONE时不返回数据。
sqlite3_step() 使用步骤,如下:
- (void)query {
sqlite3 *sqlite;
NSLog(@"%@",[NSString stringWithUTF8String:DATABASEPATH]);
int openFlag = sqlite3_open_v2(DATABASEPATH, &sqlite, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL);
if (openFlag != SQLITE_OK) {
NSLog(@"数据库打开失败!");
sqlite3_close(sqlite);
}
// +0表示将'no'转化成数字进行比较
NSString *sql = @"select * from ClientTable where no+0 > ?";
sqlite3_stmt *statement;
int prepareFlag = sqlite3_prepare_v2(sqlite, [sql UTF8String], -1, &statement, NULL);
if (prepareFlag !=SQLITE_OK) {
NSLog(@"数据库prepare失败");
}
// 获取sql中的参数个数
int paramCount = sqlite3_bind_parameter_count(statement);
// sqlite3_bind_text(statement, 1, "100", -1, nil);
// 绑定int类型的参数
sqlite3_bind_int(statement, 1, 100);
// // 执行一次就要获取一次结果,只执行而不使用column获取结果将会导致数据丢失
// int result = sqlite3_step(statement);
while (sqlite3_step(statement) == SQLITE_ROW) {
NSUInteger num_cols = (NSUInteger)sqlite3_data_count(statement);
int columnCount = sqlite3_data_count(statement);
const unsigned char *c1 = sqlite3_column_text(statement, 0);
const unsigned char *c2 = sqlite3_column_text(statement, 1);
const unsigned char *c3 = sqlite3_column_text(statement, 2);
NSLog(@"%s-%s-%s",c1,c2,c3);
}
sqlite3_finalize(statement);
sqlite3_close(sqlite);
}
sqlite3_step()中的结果处理有关的几个方法
sqlite3_bind_parameter_count:获取sql中需要绑定的参数的个数
sqlite3_column_type:获取对应列名的类型,用于动态绑定/获取参数
sqlite3_column_count:获取step执行完毕后的结果行中的列数,用于将数据进行序列化从而输出数据
sqlite3_data_count:和sqlite3_column_count大同小异
8. sqlite3_finalize()
关闭虚拟机并释放所有由prepare()函数产生的内存
9. sqlite3_close()
关闭数据库
10. sqlite3_reset()
用户一般不会多次执行完全相同的sql语句,但是会经常执行格式差不多但是参数不一样的sql语句。按照常规操作,bind后需要finalize对应的statement然后再次sqlite_prepare新的statement。但是sqlite_reset方法的效率是大于等于sqlite_prepare方法的,而sqlite_reset相当于将statement回滚到了最初的状态。所以使用reset可以显著地提高程序的性能表现。
FMDB中正是使用了这个特性,将bind之前的sql语句对应的pstmt存储起来,每次使用之前进行reset,如果没有才创建,代码如下:
if (_shouldCacheStatements && !cachedStmt) {
cachedStmt = [[FMStatement alloc] init];
[cachedStmt setStatement:pStmt];
[self setCachedStatement:cachedStmt forQuery:sql];
FMDBRelease(cachedStmt);
}
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);
}
备注
1、FMDB中的 reset 模式默认关闭,可以通过self.db.shouldCacheStatements = YES;
来开启 pstmt 缓存模式
2、在[database close]
时会清空所有数据,所以 reset 的优化效果仅限于一次 open-close 之内的操作
11. sqlite3_exec
exec 方法是对 sqlite3_prepare_v2()、sqlite3_step()、和 sqlite3_finalize()方法的封装。可以一步到位执行多个sql语句,但是无法绑定参数。使用如下:
注意点:
1、要根据返回值判断是否执行成功,因为执行错误不一定会有errMsg(具体如何使用errMsg??)
2、如果使用了errMsg,那么需要手动调用sqlite_free()方法来释放内存
代码如下:
- (void)createDB {
sqlite3 *sqlite;
int openFlage = sqlite3_open_v2(DATABASEPATH, &sqlite, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (openFlage != SQLITE_OK) {
sqlite3_close(sqlite);
NSLog(@"数据库打开失败!");
}
NSLog(@"path:%@",[NSString stringWithUTF8String:DATABASEPATH]);
NSString *deleteSql = @"delete from ClientTable";
char *err = 0;
int deleteFlag = sqlite3_exec(sqlite, [deleteSql UTF8String], NULL, NULL, &err);
if (deleteFlag != SQLITE_OK) {
NSLog(@"数据库表创建失败!");
NSLog(@"%s",err);
}
NSString *sql = @"CREATE table if not exists ClientTable (name text, no text, signature text,PRIMARY KEY(no));delete from ClientTable";
int createFlag = sqlite3_exec(sqlite, [sql UTF8String], NULL, NULL, NULL);
if (createFlag != SQLITE_OK) {
NSLog(@"数据库表创建失败!");
}
sqlite3_free(err);
int closeFlag = sqlite3_close(sqlite);
if (closeFlag != SQLITE_OK) {
NSLog(@"close错误");
}
}
12. vfs是什么?
略
仅vfs可设置的参数(sqlite3_vfs.xOpen方法):
参数名 | 值 | 使用限制 | 意义 |
---|---|---|---|
SQLITE_OPEN_DELETEONCLOSE | 0x00000008 | /* VFS only */ | ??? |
SQLITE_OPEN_EXCLUSIVE | 0x00000010 | /* VFS only */ | ??? |
SQLITE_OPEN_AUTOPROXY | 0x00000020 | /* VFS only */ | ??? |
SQLITE_OPEN_MAIN_DB | 0x00000100 | /* VFS only */ | ??? |
SQLITE_OPEN_TEMP_DB | 0x00000200 | /* VFS only */ | ??? |
SQLITE_OPEN_TRANSIENT_DB | 0x00000400 | /* VFS only */ | ??? |
SQLITE_OPEN_MAIN_JOURNAL | 0x00000800 | /* VFS only */ | ??? |
SQLITE_OPEN_TEMP_JOURNAL | 0x00001000 | /* VFS only */ | ??? |
SQLITE_OPEN_SUBJOURNAL | 0x00002000 | /* VFS only */ | ??? |
SQLITE_OPEN_MASTER_JOURNAL | 0x00004000 | /* VFS only */ | ??? |
总表:Flags For File Open Operations
URI是什么?
URI:统一资源标识符
URL:统一资源定位符,带访问方式的统一资源标识符
URN:统一资源名
三者关系:URL和URN都是是URI的子集。URN上中加上了对应的访问方式就成了URL。因为是子集,URL、URN都可以叫做URI
例如8985321是一串电话,可以认为是URN,是URN必定是URI了。但是你不用用http协议去访问电话吧?应当使用tel:+0278985321这种方式去访问,所以这里的“tel:+0278985321”就是一个URL,也可以说成是一个URI。类似的还有https:www.baidu.com,其实是访问了服务器中的index.html这种文件,只是代理吧IP处理成了域名,并将端口号等信息进行了封装
sqlite3中的URI
URI说白了就是告诉程序open的是哪个数据库文件,同时sqlite3中可以在URI中添加很多参数,这样就直接省去了在第三个参数中设置的麻烦
开启URI的三种方式:
URI的参数设置:
vfs:vfs设置
mode:设置数据库的打开方式,也就是read、write、create那几个参数
cache:缓存模式,也就是share-cache和private-cache,共享缓存模式
psow:存储介质相关,略
nolock:设置 rollback journal modes模式,参考sqlite3中的文件锁
immutable:布尔值,为yes时代表当前使用的数据库是只读类的媒体文件(例如只读光盘的刻录)。此时程序认为数据库只为以只读的方式打开,所有的锁都会失效。
官方文档:2个对象和8个方法的概览 或者说是 sqlite中的核心接口和对象