核心步骤
在 C 语言中执行任何 SQL 语句(包括 INSERT)都遵循一个标准流程:

(图片来源网络,侵删)
- 包含头文件:引入 SQLite 的 C 语言接口头文件
sqlite3.h。 - 打开数据库:使用
sqlite3_open()函数,如果数据库文件不存在,它会自动创建。 - 准备 SQL 语句:使用
sqlite3_prepare_v2()函数将 SQL 字符串编译成一个 SQLite 语句对象,这是为了提高效率和安全性(防止 SQL 注入)。 - 绑定参数:SQL 语句包含占位符(),使用
sqlite3_bind_*()系列函数将 C 语言的变量值绑定到这些占位符上。这是防止 SQL 注入的关键步骤。 - 执行语句:使用
sqlite3_step()函数来执行准备好的语句,对于INSERT语句,当sqlite3_step()返回SQLITE_DONE时,表示执行成功。 - 重置和清理:使用
sqlite3_reset()重置语句对象,以便可以重新绑定参数并执行(在循环中插入多行时非常有用),使用sqlite3_finalize()释放语句对象。 - 关闭数据库:使用
sqlite3_close()关闭数据库连接。
完整代码示例
这个示例将创建一个名为 test.db 的数据库,其中包含一个 users 表,然后向表中插入一条新记录。
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
// 回调函数,用于处理查询结果(本示例中INSERT不使用)
static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
for(int i = 0; i < argc; i++) {
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
int main(int argc, char* argv[]) {
sqlite3 *db; // 数据库连接对象
char *zErrMsg = 0; // 错误信息指针
int rc; // 返回码
// 1. 打开数据库
// 如果数据库文件不存在,sqlite3_open()会自动创建它
rc = sqlite3_open("test.db", &db);
if (rc) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
return(1);
} else {
fprintf(stdout, "Opened database successfully\n");
}
// 2. 创建表 (如果不存在)
const char *sql_create_table = "CREATE TABLE IF NOT EXISTS users ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT NOT NULL,"
"email TEXT NOT NULL UNIQUE,"
"age INTEGER);";
rc = sqlite3_exec(db, sql_create_table, callback, 0, &zErrMsg);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
} else {
fprintf(stdout, "Table created or already exists successfully\n");
}
// 3. 准备并执行 INSERT 语句 (使用参数绑定)
const char *sql_insert = "INSERT INTO users (name, email, age) VALUES (?, ?, ?);";
sqlite3_stmt *stmt; // 语句对象
// 编制 SQL 语句
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));
return 1;
}
// 绑定参数
// 第一个参数是语句对象
// 第二个参数是问号占位符的索引 (从 1 开始)
// 第三个参数是要绑定的值
// 第四个参数是值的长度 (对于以 null 结尾的字符串,使用 -1)
// 第五个参数是析构函数 (通常为 NULL,表示由 SQLite 管理)
const char *name = "张三";
const char *email = "zhangsan@example.com";
int age = 30;
// 绑定 name (TEXT)
sqlite3_bind_text(stmt, 1, name, -1, NULL);
// 绑定 email (TEXT)
sqlite3_bind_text(stmt, 2, email, -1, NULL);
// 绑定 age (INTEGER)
sqlite3_bind_int(stmt, 3, age);
// 执行语句
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db));
} else {
fprintf(stdout, "Records inserted successfully\n");
// 获取最后插入行的 rowid
fprintf(stdout, "Last inserted rowid: %lld\n", sqlite3_last_insert_rowid(db));
}
// 4. 清理语句对象
sqlite3_finalize(stmt);
// 5. 关闭数据库
sqlite3_close(db);
return 0;
}
如何编译和运行
-
保存代码:将上面的代码保存为
insert_example.c。 -
编译:你需要安装 SQLite 的开发库(通常叫做
libsqlite3-dev或sqlite3-devel),然后使用以下命令编译:# Linux / macOS gcc insert_example.c -o insert_example -lsqlite3 # Windows (如果你使用 MinGW) # gcc insert_example.c -o insert_example.exe -lsqlite3
-lsqlite3告诉链接器链接 SQLite 库。
-
运行:
(图片来源网络,侵删)./insert_example
预期输出:
Opened database successfully
Table created or already exists successfully
Records inserted successfully
Last inserted rowid: 1
运行后,你会在同一目录下看到一个 test.db 文件,这就是你的 SQLite 数据库文件。
关键点详解
防止 SQL 注入的重要性
错误的做法(不安全):
char *name = "some_name";
char *sql = "INSERT INTO users (name) VALUES ('" name "');"; // 拼接字符串
sqlite3_exec(db, sql, 0, 0, &zErrMsg);
name 的值是 Robert'); DROP TABLE users; --,那么你的 SQL 语句就变成了:

(图片来源网络,侵删)
INSERT INTO users (name) VALUES ('Robert'); DROP TABLE users; --');
这会执行两个操作:插入一个名为 "Robert" 的用户,然后删除整个 users 表!这是灾难性的。
正确的做法(安全):
使用 作为占位符,然后用 sqlite3_bind_*() 函数绑定变量,SQLite 会安全地处理这些值,确保它们被当作数据而不是 SQL 代码的一部分来处理。
sqlite3_exec() vs. sqlite3_prepare_v2() + sqlite3_step()
-
sqlite3_exec():- 这是一个“便捷”函数,封装了“准备-执行-重置-清理”的全过程。
- 适用于简单的、没有参数或一次性执行的 SQL 语句。
- 对于
INSERT,如果只是插入一条固定数据,用它很方便。 - 代码示例:
const char *sql = "INSERT INTO users (name, email) VALUES ('李四', 'lisi@example.com');"; rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg);
-
sqlite3_prepare_v2()+sqlite3_step():- 这是更底层、更灵活、更强大的方式。
- 必须用于带参数的 SQL 语句,这是防止 SQL 注入的唯一标准方法。
- 性能更好,因为 SQL 语句只编译一次,可以多次执行(通过循环绑定不同参数)。
- 我们上面展示的完整示例就是使用这种方式。
在循环中插入多行
如果你想高效地插入大量数据,应该重用 sqlite3_stmt 对象。
// ... (前面的打开、创建表代码相同)
const char *sql_insert = "INSERT INTO users (name, email, age) VALUES (?, ?, ?);";
sqlite3_stmt *stmt;
rc = sqlite3_prepare_v2(db, sql_insert, -1, &stmt, NULL);
// 假设你有一个数据源
char *names[] = {"王五", "赵六", "钱七"};
char *emails[] = {"wangwu@example.com", "zhaoliu@example.com", "qianqi@example.com"};
int ages[] = {25, 32, 28};
int num_users = 3;
for (int i = 0; i < num_users; i++) {
// 绑定新的参数值
sqlite3_bind_text(stmt, 1, names[i], -1, NULL);
sqlite3_bind_text(stmt, 2, emails[i], -1, NULL);
sqlite3_bind_int(stmt, 3, ages[i]);
// 执行
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Error inserting row %d: %s\n", i, sqlite3_errmsg(db));
break; // 或者继续处理其他行
}
// 重置语句,以便下一次循环可以再次绑定和执行
sqlite3_reset(stmt);
}
// ... (清理和关闭代码)
sqlite3_finalize(stmt);
sqlite3_close(db);
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
sqlite3_exec() |
简单、代码少 | 无法安全地绑定变量,性能较低 | 一次性执行、无参数的简单 SQL(如建表、查询单条固定数据) |
prepare/step |
安全(防注入)、高性能(可复用)、灵活 | 代码稍多,需要手动管理内存 | 所有带参数的 SQL 操作,特别是 INSERT、UPDATE、DELETE 以及需要循环插入的场景 |
对于任何严肃的 C 语言 SQLite 开发,强烈推荐使用 sqlite3_prepare_v2() + sqlite3_step() + sqlite3_bind_*() 的组合。
