2. SQLite核心API:sqlite3_open、sqlite3_close、sqlite3_exec、sqlite3_prepare_v2、sqlite3_step、sqlite3_finalize

好,咱们直接进入正题。SQLite 的 API 其实不多,核心的就那么几个。你只要把这六个函数吃透了,日常开发基本够用。我当年刚接触嵌入式数据库时,就是靠这几个函数撑起了一个完整的日志采集系统。今天我把它们掰开揉碎了讲给你听。

核心要点:这六个函数分为三组——打开/关闭快捷执行预编译执行。理解它们的分工,你就掌握了 SQLite 的命脉。

SQLite 核心 API 调用流程 sqlite3_open() 打开数据库连接 sqlite3_exec() 快捷执行(无结果集) sqlite3_prepare_v2() 预编译(有结果集) sqlite3_step() 逐行获取结果 sqlite3_finalize() 释放预编译语句 sqlite3_close() 关闭数据库连接

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