2. SQLite核心API:sqlite3_open、sqlite3_close、sqlite3_exec、sqlite3_prepare_v2、sqlite3_step、sqlite3_finalize
好,咱们直接进入正题。SQLite 的 API 其实不多,核心的就那么几个。你只要把这六个函数吃透了,日常开发基本够用。我当年刚接触嵌入式数据库时,就是靠这几个函数撑起了一个完整的日志采集系统。今天我把它们掰开揉碎了讲给你听。
核心要点:这六个函数分为三组——打开/关闭、快捷执行、预编译执行。理解它们的分工,你就掌握了 SQLite 的命脉。
2.1 sqlite3_open —— 打开数据库的大门
这个函数负责建立与数据库文件的连接。说白了,就是告诉 SQLite:「嘿,我要操作这个文件了。」
int sqlite3_open(
const char *filename, // 数据库文件路径
sqlite3 **ppDb // 输出参数:数据库连接句柄
);
如果文件不存在,它会自动创建。嗯,这里要注意:如果你传的是 :memory:,它会在内存中创建临时数据库,速度飞快,但程序退出后数据就没了。我在项目中经常用这个做单元测试,跑完自动销毁,省得清理垃圾文件。
个人习惯:每次调用 sqlite3_open 后,我都会立即检查返回值。如果返回 SQLITE_OK 以外的值,赶紧调用 sqlite3_errmsg() 看错误信息。别偷懒,这个习惯救过我很多次。
2.2 sqlite3_close —— 记得关门
有开就有关。这个函数释放数据库连接占用的资源。
int sqlite3_close(sqlite3 *db);
我曾经犯过一个低级错误:在还有未 finalize 的预编译语句时直接调用 close,结果返回 SQLITE_BUSY。后来养成习惯,关闭前先确保所有语句都释放了。
避坑指南:如果 close 返回 SQLITE_BUSY,说明还有未释放的 prepared statement 或者未完成的回调。别硬关,先排查资源泄漏。
2.3 sqlite3_exec —— 一把梭的快捷方式
这个函数适合执行那些不需要返回结果集的 SQL,比如 CREATE TABLE、INSERT、UPDATE、DELETE。
int sqlite3_exec(
sqlite3 *db, // 数据库连接
const char *sql, // 要执行的 SQL 语句
sqlite3_callback callback, // 回调函数(通常传 NULL)
void *arg, // 回调函数的第一个参数
char **errmsg // 错误信息输出
);
它内部其实封装了 prepare、step、finalize 三步。你想想看,如果只是建个表,何必自己写三步?一把梭就完了。
实战经验:我在做嵌入式设备的数据采集时,大量使用 exec 来批量插入数据。但注意,如果插入量很大(比如上万条),建议用事务包裹,否则每条 insert 都会触发一次磁盘同步,慢得你想哭。
2.4 sqlite3_prepare_v2 —— 预编译,效率的保证
当你需要执行查询并获取结果集时,就得用这个函数了。它把 SQL 语句编译成字节码,后续可以反复执行。
int sqlite3_prepare_v2(
sqlite3 *db, // 数据库连接
const char *sql, // SQL 语句
int nByte, // SQL 长度(传 -1 表示自动计算)
sqlite3_stmt **ppStmt, // 输出:预编译语句句柄
const char **pzTail // 指向未处理的 SQL 部分
);
为什么用 v2 版本?因为 v1 版本有一些行为不一致的问题。我个人建议:永远用 v2,别碰 v1。
小技巧:如果同一个 SQL 要执行多次(比如循环插入),用 prepare_v2 一次,然后多次 step,最后 finalize 一次。这比每次 exec 快一个数量级。
2.5 sqlite3_step —— 一步步来
这个函数驱动预编译语句执行。对于查询语句,每次调用返回一行结果;对于 DML 语句,调用一次就完成执行。
int sqlite3_step(sqlite3_stmt *stmt);
返回值主要有三个:
| 返回值 | 含义 | 下一步操作 |
|---|---|---|
| SQLITE_ROW | 还有一行数据 | 用 sqlite3_column_xxx 读取,然后继续 step |
| SQLITE_DONE | 执行完毕,没有更多数据 | 调用 finalize 释放 |
| SQLITE_BUSY | 数据库被锁住 | 稍后重试 |
我记得有一次调试一个多线程程序,死活查不到数据,后来发现是忘了循环调用 step 直到返回 SQLITE_DONE。嗯,这种低级错误,犯过一次就记住了。
2.6 sqlite3_finalize —— 善后工作
这个函数释放预编译语句占用的所有资源。不调用它,就会内存泄漏。
int sqlite3_finalize(sqlite3_stmt *stmt);
我曾经:在一个长期运行的服务中忘记 finalize,结果内存以肉眼可见的速度增长。排查了两天才发现是这里的问题。从那以后,我写代码时都是 prepare 和 finalize 成对出现,就像 malloc 和 free 一样。
完整示例:查询用户表
下面是一个完整的查询示例,把上面六个函数串起来:
#include <stdio.h>
#include <sqlite3.h>
int main() {
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
// 1. 打开数据库
rc = sqlite3_open("test.db", &db);
if (rc != SQLITE_OK) {
printf("打开失败: %s\n", sqlite3_errmsg(db));
return 1;
}
// 2. 用 exec 建表
const char *sql_create = "CREATE TABLE IF NOT EXISTS users (id INT, name TEXT);";
rc = sqlite3_exec(db, sql_create, 0, 0, 0);
if (rc != SQLITE_OK) {
printf("建表失败: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 3. 用 exec 插入数据
rc = sqlite3_exec(db, "INSERT INTO users VALUES(1, 'Alice'), (2, 'Bob');", 0, 0, 0);
if (rc != SQLITE_OK) {
printf("插入失败: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 4. 预编译查询
const char *sql_query = "SELECT id, name FROM users;";
rc = sqlite3_prepare_v2(db, sql_query, -1, &stmt, 0);
if (rc != SQLITE_OK) {
printf("预编译失败: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
// 5. 逐行获取结果
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
const unsigned char *name = sqlite3_column_text(stmt, 1);
printf("id=%d, name=%s\n", id, name);
}
// 6. 释放预编译语句
sqlite3_finalize(stmt);
// 7. 关闭数据库
sqlite3_close(db);
return 0;
}
总结一下:这六个函数构成了 SQLite 编程的骨架。open 和 close 是生命周期管理,exec 是快捷方式,prepare/step/finalize 是精细控制。你只要记住:有 prepare 就必须有 finalize,有 open 就必须有 close。这个原则比什么都重要。
公众号:蓝海资料掘金营,微信deep3321