SQLite C语言API如何高效使用?

99ANYc3cd6
预计阅读时长 33 分钟
位置: 首页 C语言 正文

SQLite 是一个轻量级的、无服务器的、基于文件的 SQL 数据库引擎,它通过一个 C 语言库提供接口,这意味着你可以直接在 C/C++ 项目中调用这些函数来操作数据库。

sqlite c语言api
(图片来源网络,侵删)

核心概念

在深入 API 之前,必须理解几个核心概念:

  1. 数据库连接:每个与数据库文件的交互都通过一个 sqlite3* 类型的指针来表示,这个指针代表一个打开的数据库连接,所有后续的数据库操作(如执行 SQL 语句、查询等)都需要这个连接句柄。
  2. 预处理语句:当你执行一条 SQL 语句时,SQLite 不会直接执行它,而是先将其编译成一个内部的、高效的字节码格式,这个编译后的结果就存储在一个 sqlite3_stmt* 类型的指针中,称为“预处理语句”或“语句句柄”,使用预处理语句可以大大提高重复执行相同 SQL 语句的效率。
  3. 回调函数:对于 SELECT 查询这类会返回多行数据的语句,SQLite 需要一种方式将每一行的数据传递给你的程序,最传统的方式是提供一个回调函数,当 SQLite 获取到一行数据后,它会调用你提供的这个函数,并将该行的数据作为参数传入。
  4. 内存管理:SQLite API 在分配内存时非常严格,你需要为每个字符串(如 SQL 语句、错误信息)提供足够的缓冲区空间,或者让 SQLite 为你分配内存(此时你需要负责释放)。释放内存是 C 语言编程中最重要的环节,也是最容易出错的地方。

核心 API 函数(按操作流程)

下面是使用 SQLite C API 的典型步骤,以及每个步骤对应的关键函数。

打开/创建数据库

int sqlite3_open(
  const char *filename,   // 数据库文件名. ":memory:" 表示内存数据库
  sqlite3 **ppDb          // 输出参数,用于接收数据库连接句柄
);
  • 功能:打开一个数据库文件,如果文件不存在,则创建它,如果使用 memory:,则会在内存中创建一个临时数据库。
  • 返回值SQLITE_OK (0) 表示成功,否则是一个错误码。
  • 示例
    sqlite3 *db;
    int rc = sqlite3_open("test.db", &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        return 1;
    }
    printf("Database opened successfully.\n");

执行 SQL 语句(简单方式)

对于不返回结果集的简单 SQL 语句(如 CREATE TABLE, INSERT, UPDATE, DELETE),可以使用 sqlite3_exec,它是一个封装了多个步骤的便捷函数。

int sqlite3_exec(
  sqlite3*           db,            // 数据库连接
  const char *sql,                  // 要执行的 SQL 语句
  sqlite3_callback  callback,      // 回调函数 (用于处理结果集,此处可为 NULL)
  void *           arg,            // 传递给回调函数的参数
  char **errmsg                  // 输出参数,用于存储错误信息
);
  • 功能:执行一条或多条 SQL 语句。
  • 参数
    • callback: SQL 是 SELECT 查询,你需要提供一个函数指针,如果不需要处理结果(如 INSERT),则传入 NULL
    • arg: 一个自定义指针,会原封不动地传递给 callback 函数。
    • errmsg: 如果执行失败,这里会指向一个描述错误的字符串。调用者需要负责使用 sqlite3_free() 释放这个字符串。
  • 示例
    // 创建一个表
    const char *sql = "CREATE TABLE IF NOT EXISTS Users ("
                      "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                      "name TEXT NOT NULL,"
                      "age INTEGER);";
    char *errMsg = NULL;
    rc = sqlite3_exec(db, sql, NULL, NULL, &errMsg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "SQL error: %s\n", errMsg);
        sqlite3_free(errMsg); // 务必释放错误信息
    } else {
        printf("Table created successfully.\n");
    }

准备、绑定、执行和重置(高级方式)

对于需要参数或需要重复执行的 SQL 语句,推荐使用下面这组函数,它们更灵活、更高效。

sqlite c语言api
(图片来源网络,侵删)

a. 准备语句

int sqlite3_prepare_v2(
  sqlite3* db,            // 数据库连接
  const char *zSql,       // SQL 语句
  int nByte,              // SQL 语句的最大长度 (如果为 -1, 则自动计算)
  sqlite3_stmt **ppStmt,  // 输出参数,用于接收预处理语句句柄
  const char **pzTail     // 输出参数,指向未使用的 SQL (用于分步解析)
);
  • 功能:将 SQL 语句编译成一个预处理语句。
  • 示例
    const char *sql = "INSERT INTO Users (name, age) VALUES (?, ?);";
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        // 错误处理
    }

b. 绑定参数

使用 作为占位符,然后用 sqlite3_bind_* 系列函数将 C 变量的值绑定到这些占位符上。

  • sqlite3_bind_int(stmt, index, value)

  • sqlite3_bind_text(stmt, index, text, n, SQLITE_STATIC)

  • sqlite3_bind_double(stmt, index, value)

    sqlite c语言api
    (图片来源网络,侵删)
  • sqlite3_bind_blob(stmt, index, blob, n, SQLITE_STATIC)

  • sqlite3_bind_null(stmt, index)

  • 参数

    • stmt: 预处理语句句柄。
    • index: 参数的索引,从 1 开始。
    • n: 对于文本和 BLOB,是数据的长度,对于以 \0 结尾的 C 字符串,可以使用 SQLITE_STATIC 告诉 SQLite 不要复制字符串,而是直接使用你的指针(前提是这个指针的生命周期比语句长),如果不确定,可以使用 SQLITE_TRANSIENT,让 SQLite 复制一份数据。
  • 示例

    const char *name = "Alice";
    int age = 30;
    // 绑定第一个参数 (name)
    sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
    // 绑定第二个参数 (age)
    sqlite3_bind_int(stmt, 2, age);

c. 执行语句

int sqlite3_step(sqlite3_stmt *pStmt);
  • 功能:执行预处理语句。
  • 返回值
    • SQLITE_DONE: 语句执行完成(通常用于 INSERT, UPDATE, DELETE)。
    • SQLITE_ROW: 语句返回了一行数据(用于 SELECT 查询)。
    • 其他错误码。
  • 示例
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE) {
        // 错误处理
    } else {
        printf("Insert successful.\n");
    }

d. 重置和清理

int sqlite3_reset(sqlite3_stmt *pStmt); // 重置语句,以便可以重新绑定参数并再次执行
void sqlite3_finalize(sqlite3_stmt *pStmt); // 销毁预处理语句,释放资源
  • reset: 在 INSERT 循环中非常有用,执行完 step 后,调用 reset,然后可以绑定新的参数,再调用 step
  • finalize: 当你不再需要某个预处理语句时,必须调用此函数来释放它占用的内存。
  • 示例
    // 循环插入多条数据
    for (int i = 0; i < 5; i++) {
        sqlite3_bind_text(stmt, 1, "Bob", -1, SQLITE_STATIC);
        sqlite3_bind_int(stmt, 2, 25 + i);
        sqlite3_step(stmt);
        sqlite3_reset(stmt); // 重置,准备下一次循环
    }
    sqlite3_finalize(stmt); // 销毁语句

查询数据(获取结果)

sqlite3_step 返回 SQLITE_ROW 时,你可以使用以下函数从当前行中获取数据。

// 获取列的数据类型
int sqlite3_column_type(sqlite3_stmt*, int iCol);
// 获取列的值
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); // TEXT
int sqlite3_column_int(sqlite3_stmt*, int iCol);                   // INTEGER
double sqlite3_column_double(sqlite3_stmt*, int iCol);             // REAL
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);          // BLOB
int sqlite3_column_bytes(sqlite3_stmt*, int iCol);                 // BLOB/TEXT 的长度
// 获取列的名称
const char *sqlite3_column_name(sqlite3_stmt*, int iCol);
  • 参数
    • iCol: 列的索引,从 0 开始。
  • 示例
    const char *sql = "SELECT id, name, age FROM Users;";
    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            int id = sqlite3_column_int(stmt, 0);
            const unsigned char *name = sqlite3_column_text(stmt, 1);
            int age = sqlite3_column_int(stmt, 2);
            printf("ID: %d, Name: %s, Age: %d\n", id, name, age);
        }
    }
    sqlite3_finalize(stmt);

关闭数据库

int sqlite3_close(sqlite3 *db);
  • 功能:关闭数据库连接,释放所有相关资源。
  • 注意:在关闭连接之前,必须确保所有预处理语句都已经通过 sqlite3_finalize 被销毁了,否则,sqlite3_close 会返回 SQLITE_BUSY 错误。
  • 示例
    sqlite3_close(db);

完整示例代码

下面是一个将上述所有步骤串联起来的完整 C 程序。

#include <stdio.h>
#include <sqlite3.h>
int main() {
    sqlite3 *db;
    char *errMsg = NULL;
    int rc;
    // 1. 打开数据库
    rc = sqlite3_open("test.db", &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        return 1;
    }
    printf("Database opened successfully.\n");
    // 2. 创建表 (使用 sqlite3_exec)
    const char *sql_create = "CREATE TABLE IF NOT EXISTS Users ("
                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                             "name TEXT NOT NULL,"
                             "age INTEGER);";
    rc = sqlite3_exec(db, sql_create, NULL, 0, &errMsg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "SQL error: %s\n", errMsg);
        sqlite3_free(errMsg);
    } else {
        printf("Table created successfully.\n");
    }
    // 3. 插入数据 (使用 prepare, bind, step, finalize)
    const char *sql_insert = "INSERT INTO Users (name, age) VALUES (?, ?);";
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(db, sql_insert, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
    } else {
        // 插入 Alice
        sqlite3_bind_text(stmt, 1, "Alice", -1, SQLITE_STATIC);
        sqlite3_bind_int(stmt, 2, 30);
        sqlite3_step(stmt);
        sqlite3_reset(stmt);
        // 插入 Bob
        sqlite3_bind_text(stmt, 1, "Bob", -1, SQLITE_STATIC);
        sqlite3_bind_int(stmt, 2, 25);
        sqlite3_step(stmt);
        sqlite3_finalize(stmt); // 销毁语句
        printf("Data inserted successfully.\n");
    }
    // 4. 查询数据 (使用 prepare, step, column_* functions)
    const char *sql_select = "SELECT id, name, age FROM Users;";
    rc = sqlite3_prepare_v2(db, sql_select, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        printf("\n--- User Data ---\n");
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            printf("ID: %d, Name: %s, Age: %d\n",
                   sqlite3_column_int(stmt, 0),
                   sqlite3_column_text(stmt, 1),
                   sqlite3_column_int(stmt, 2));
        }
        sqlite3_finalize(stmt);
    }
    // 5. 关闭数据库
    sqlite3_close(db);
    printf("\nDatabase closed.\n");
    return 0;
}

编译和运行

  1. 确保安装了 SQLite 开发包,在 Linux (Debian/Ubuntu) 上,可以使用 sudo apt-get install libsqlite3-dev
  2. 保存代码:保存为 sqlite_example.c
  3. 编译:使用 gcc 并链接 sqlite3 库。
    gcc sqlite_example.c -o sqlite_example -lsqlite3
  4. 运行
    ./sqlite_example

你会看到输出,并且当前目录下会生成一个 test.db 文件。

总结与最佳实践

  • 错误处理:几乎所有的 SQLite API 函数都会返回一个整数错误码,在关键操作后,务必检查返回值是否为 SQLITE_OK
  • 资源释放sqlite3_free() 用于释放 sqlite3_execsqlite3_column_* 返回的字符串。sqlite3_finalize() 用于释放 sqlite3_stmt*sqlite3_close() 用于释放 sqlite3*,遵循“谁分配,谁释放”的原则,避免内存泄漏。
  • 使用 sqlite3_prepare_v2:对于生产环境,强烈推荐使用 prepare/step/finalize 模式,而不是 sqlite3_exec,因为它更安全(可以防止 SQL 注入,通过绑定参数)、更灵活(可以处理二进制数据)且性能更高(对于重复执行的语句)。
  • 线程安全:SQLite 默认是线程安全的,但有一个限制:同一个数据库连接 (sqlite3*) 不能被多个线程同时使用,每个线程应该有自己的连接句柄,或者,可以配置 SQLite 在多线程模式下运行,但每个连接仍然只能被一个线程使用。
-- 展开阅读全文 --
头像
织梦文件上传路径怎么换?
« 上一篇 01-22
公布暴dede模板路径的方法有何风险?
下一篇 » 01-22

相关文章

取消
微信二维码
支付宝二维码

目录[+]