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

(图片来源网络,侵删)
核心概念
在深入 API 之前,必须理解几个核心概念:
- 数据库连接:每个与数据库文件的交互都通过一个
sqlite3*类型的指针来表示,这个指针代表一个打开的数据库连接,所有后续的数据库操作(如执行 SQL 语句、查询等)都需要这个连接句柄。 - 预处理语句:当你执行一条 SQL 语句时,SQLite 不会直接执行它,而是先将其编译成一个内部的、高效的字节码格式,这个编译后的结果就存储在一个
sqlite3_stmt*类型的指针中,称为“预处理语句”或“语句句柄”,使用预处理语句可以大大提高重复执行相同 SQL 语句的效率。 - 回调函数:对于
SELECT查询这类会返回多行数据的语句,SQLite 需要一种方式将每一行的数据传递给你的程序,最传统的方式是提供一个回调函数,当 SQLite 获取到一行数据后,它会调用你提供的这个函数,并将该行的数据作为参数传入。 - 内存管理: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 语句,推荐使用下面这组函数,它们更灵活、更高效。

(图片来源网络,侵删)
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)
(图片来源网络,侵删) -
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;
}
编译和运行
- 确保安装了 SQLite 开发包,在 Linux (Debian/Ubuntu) 上,可以使用
sudo apt-get install libsqlite3-dev。 - 保存代码:保存为
sqlite_example.c。 - 编译:使用
gcc并链接sqlite3库。gcc sqlite_example.c -o sqlite_example -lsqlite3
- 运行:
./sqlite_example
你会看到输出,并且当前目录下会生成一个 test.db 文件。
总结与最佳实践
- 错误处理:几乎所有的 SQLite API 函数都会返回一个整数错误码,在关键操作后,务必检查返回值是否为
SQLITE_OK。 - 资源释放:
sqlite3_free()用于释放sqlite3_exec或sqlite3_column_*返回的字符串。sqlite3_finalize()用于释放sqlite3_stmt*。sqlite3_close()用于释放sqlite3*,遵循“谁分配,谁释放”的原则,避免内存泄漏。 - 使用
sqlite3_prepare_v2:对于生产环境,强烈推荐使用prepare/step/finalize模式,而不是sqlite3_exec,因为它更安全(可以防止 SQL 注入,通过绑定参数)、更灵活(可以处理二进制数据)且性能更高(对于重复执行的语句)。 - 线程安全:SQLite 默认是线程安全的,但有一个限制:同一个数据库连接 (
sqlite3*) 不能被多个线程同时使用,每个线程应该有自己的连接句柄,或者,可以配置 SQLite 在多线程模式下运行,但每个连接仍然只能被一个线程使用。
