liangbm3's blog

Back

1. 核心概念#

在使用 C++ API 之前,需要理解 SQLite3 的几个核心对象和函数:

  • sqlite3: 代表一个打开的数据库连接的句柄(handle)。所有针对该数据库的操作都需要这个对象。
  • sqlite3_stmt: 代表一个已经编译(prepared)的 SQL 语句。这是执行复杂查询和防止 SQL 注入的关键。
  • 返回码: SQLite3 的几乎所有函数都会返回一个整型状态码,例如 SQLITE_OK 表示成功,SQLITE_ERROR 表示通用错误,SQLITE_BUSY 表示数据库被锁定等。检查这些返回码是保证程序健壮性的关键。

2. 经典使用流程#

2.1 包含头文件和定义必要变量#

int main() {
  sqlite3 *db; // 数据库句柄
  char *errMsg = nullptr;
  int rc; // 返回值
  //...其他代码
 }
cpp

2.2 打开或创建数据库#

函数: int sqlite3_open(const char *filename, sqlite3 **ppDb)

作用: 打开一个数据库连接。

参数:

  • filename: 数据库文件的路径。
  • ppDb: 一个指向 sqlite3* 指针的指针,函数会把创建的数据库句柄写入这里。
// 创建或打开数据库
rc = sqlite3_open("test.db", &db);
if (rc) {
  std::cerr << "Can't open database: " << sqlite3_errmsg(db) << std::endl;
  return rc;
} else {
  std::cout << "Opened database successfully" << std::endl;
}
cpp

sqlite3_errmsg(db) 是一个非常有用的函数,它可以获取最近一次操作失败的错误信息。

2.3 执行SQL语句(非查询类)#

对于不需要返回结果集的 SQL 语句,比如 CREATE TABLE, INSERT, UPDATE, DELETE,最简单的方式是使用 sqlite3_exec()

函数: int sqlite3_exec(sqlite3 *db, const char *sql, int (*callback)(void*,int,char**,char**), void *arg, char **errmsg)

作用: 一次性执行一个或多个 SQL 语句。

参数:

  • db: 数据库句柄。
  • sql: 要执行的 SQL 字符串。
  • callback: 回调函数,每当查询产生一行结果时被调用(对于非查询语句,设为 NULL)。
  • arg: 传递给回调函数的第一个参数。
  • errmsg: 用于存放错误信息的指针。
// 创建一张表
const char *sql_create_table = "CREATE TABLE IF NOT EXISTS USERS("
                             "ID INT PRIMARY KEY     NOT NULL,"
                             "NAME           TEXT    NOT NULL,"
                             "AGE            INT     NOT NULL);";
rc = sqlite3_exec(db, sql_create_table, nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK) {
  std::cerr << "SQL error: " << errMsg << std::endl;
  sqlite3_free(errMsg); // 释放错误信息内存
} else {
  std::cout << "Table created successfully" << std::endl;
}
// 插入数据
const char *sql_insert =
  "INSERT INTO USERS (ID,NAME,AGE) VALUES (1, 'Alice', 25);"
  "INSERT INTO USERS (ID,NAME,AGE) VALUES (2, 'Bob', 30);";
rc = sqlite3_exec(db, sql_insert, nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK) {
  std::cerr << "SQL error: " << errMsg << std::endl;
  sqlite3_free(errMsg); // 释放错误信息内存
} else {
  std::cout << "Records created successfully" << std::endl;
}
cpp

2.4 执行查询并处理结果#

对于 SELECT 查询,虽然也可以使用 sqlite3_exec 配合回调函数,但更经典和推荐的做法是使用 prepare - step - finalize 流程。这种方式更灵活、更安全(可以防止 SQL 注入),并且效率更高。

2.4.1 准备SQL语句#

使用 sqlite3_prepare_v2() 将 SQL 语句编译成一个字节码对象 (sqlite3_stmt)。

函数: int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail)

作用: 编译 SQL 语句,为执行做准备。

参数:

  • db: 数据库连接句柄
  • zSql: 要编译的SQL语句字符串
  • bByte:SQL字符串的字节长度,如果为 -1 SQLite 会自动计数
  • ppStmt: 输出参数,用于接收编译后的语句句柄
  • pzTail: 输出参数,指向SQL字符串中未被处理的部分,如果SQL字符串中包含多个语句,会返回第一个语句后剩余的部分,如果只有一个语句或者不关心剩余的部分,可以传入nullptr
sqlite3_stmt *stmt; // 语句句柄
const char *sql_select = "SELECT ID, NAME, AGE FROM USERS WHERE AGE > ?;";
// 准备SQL语句
rc = sqlite3_prepare_v2(db, sql_select, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
  std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl;
  sqlite3_close(db);
  return rc;
}
cpp

2.4.2 绑定参数#

如果 SQL 语句中使用了占位符 (?),需要使用 sqlite3_bind_*() 系列函数来绑定具体的值。这可以有效防止 SQL 注入攻击。

// 绑定整数
int sqlite3_bind_int(sqlite3_stmt*, int, int);

// 绑定64位整数
int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);

// 绑定双精度浮点数
int sqlite3_bind_double(sqlite3_stmt*, int, double);

// 绑定空值
int sqlite3_bind_null(sqlite3_stmt*, int);

// 绑定文本字符串
int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int, void(*)(void*));

// 绑定二进制数据
int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int, void(*)(void*));
cpp

这一系列的函数第一个参数为语句句柄,第二个参数为占位符索引(从1开始),第三个参数为值,第四个参数为一个析构函数指针,用于指定字符串的生命周期类型。这个参数可以为SQLITE_STATICSQLITE_TRANSIENT

  1. SQLITE_STATIC:
    1. 含义: 向 SQLite 保证,传入的这个字符串 (zData) 的内存是静态的、全局的、或者至少在 sqlite3_stmt 整个生命周期内(直到 sqlite3_finalize 被调用)都是有效且不会改变的。
    2. SQLite 的行为: 既然做了这个保证,SQLite 为了追求极致的性能,会直接使用传入的指针,而不会自己复制一份字符串数据。它只是持有了这个指针。
    3. 风险: 如果传入了一个局部变量(比如一个函数内的 std::string.c_str() 或者一个栈上的字符数组),当函数返回后,这个局部变量被销毁,内存被释放。但 SQLite 里的语句句柄 stmt 还傻傻地存着那个已经无效的野指针。后面调用 sqlite3_step() 去执行它时,它会尝试读取一个无效的内存地址,从而导致未定义行为,通常表现为程序崩溃、数据损坏或奇怪的错误。
  2. SQLITE_TRANSIENT:
    1. 含义: 这个宏告诉 SQLite,传入的这个字符串 (zData) 是临时的、短暂的 (transient),它的生命周期可能很短,在 sqlite3_bind_text 函数调用返回后可能就失效了。
    2. SQLite 的行为: 收到这个信号后,SQLite 会变得很谨慎。它会在函数内部立即将字符串数据完整地复制一份,并由自己来管理这份拷贝的内存。这样,即使原始的字符串变量超出了作用域被销毁,也完全没关系,因为 SQLite 使用的是它自己的安全拷贝。
    3. 代价: 需要一次额外的内存分配和数据拷贝,相比 SQLITE_STATIC 会有微小的性能开销。但它换来的是安全和省心
int age_limit = 20;
rc = sqlite3_bind_int(stmt, 1, age_limit);
if (rc != SQLITE_OK) {
  std::cerr << "Failed to bind parameter: " << sqlite3_errmsg(db) << std::endl;
  sqlite3_finalize(stmt);
  sqlite3_close(db);
  return rc;
}
cpp

2.4.3 执行语句并逐行获取结果#

使用 sqlite3_step() 来执行预处理语句。

  • 函数: int sqlite3_step(sqlite3_stmt *pStmt)
  • 作用: 执行一次编译好的 SQL 语句。
  • 返回值:
    • SQLITE_ROW: 表示成功获取了一行数据,可以提取结果。
    • SQLITE_DONE: 表示所有行都已处理完毕。
    • 其他错误码。

sqlite3_step() 返回 SQLITE_ROW 时,使用 sqlite3_column_*() 系列函数来获取当前行各个字段的值。

  • 函数: sqlite3_column_int(), sqlite3_column_text(), sqlite3_column_double() 等。
  • 作用: 获取查询结果中当前行的某一列数据。
// 循环执行step,逐行获取数据
while ((rc = 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);
  std::cout << "ID: " << id << ", Name: " << name << ", Age: " << age << std::endl;
}
if (rc != SQLITE_DONE) {
  std::cerr << "Failed to execute statement: " << sqlite3_errmsg(db) << std::endl;
}
cpp

2.5 销毁语句句柄#

查询结束后,必须使用 sqlite3_finalize() 来销毁 sqlite3_stmt 对象,释放资源。

  • 函数: int sqlite3_finalize(sqlite3_stmt *pStmt)
  • 作用: 销毁预处理语句对象,回收内存。
// 释放语句句柄
sqlite3_finalize(stmt);
cpp

2.6 关闭数据库#

所有操作完成后,使用 sqlite3_close() 关闭数据库连接,释放所有与该连接相关的资源。

  • 函数: int sqlite3_close(sqlite3 *db)
  • 作用: 关闭数据库连接。
// 关闭数据库
sqlite3_close(db);
cpp

3. 总结#

步骤核心函数主要作用
1. 打开数据库sqlite3_open()获取数据库连接句柄 sqlite3*
2. 执行非查询SQLsqlite3_exec()简单快速地执行 CREATE, INSERT 等操作
3. 准备查询sqlite3_prepare_v2()编译 SELECT 语句,获取语句句柄 sqlite3_stmt*
4. 绑定参数sqlite3_bind_*()(可选) 为 ? 占位符安全地绑定值。
5. 逐行执行sqlite3_step()在循环中调用,每次获取一行结果 (SQLITE_ROW)。
6. 提取数据sqlite3_column_*()从当前行中按列索引提取数据。
7. 终结语句sqlite3_finalize()释放语句句柄 sqlite3_stmt* 的资源。
8. 关闭数据库sqlite3_close()释放数据库连接句柄 sqlite3* 的所有资源。
C++ 中使用 SQLite3 API 的经典流程
https://liangbm3.site/blog/c-zhong-shi-yong-sqlite3-api-de-jing-dian-liu-cheng
Author liangbm3
Published at 2025年10月16日
Comment seems to stuck. Try to refresh?✨