5. INSERT插入数据实战:单行插入、多行插入、参数化绑定(sqlite3_bind_*)、批量插入性能优化

插入数据,听起来很简单对吧?不就是一条 INSERT INTO 语句嘛。但说实话,我在实际项目中踩过的坑,有一半都跟插入有关。你想想看,一个系统跑着跑着,突然写入变慢了,或者干脆写不进去了——这种问题排查起来特别头疼。

今天我就把 SQLite 插入数据的几种方式,从最基础的单行插入,到性能优化的批量操作,一次性给你讲透。嗯,咱们直接上干货。

5.1 单行插入:最基础的操作

先来个最简单的。单行插入就是一次插入一条记录。代码长这样:

#include <sqlite3.h>
#include <stdio.h>

int main() {
    sqlite3 *db;
    char *err_msg = 0;
    
    // 打开数据库
    sqlite3_open("test.db", &db);
    
    // 单行插入
    char *sql = "INSERT INTO users(id, name, age) VALUES(1, '张三', 25);";
    
    int rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "插入失败: %s\n", err_msg);
        sqlite3_free(err_msg);
    } else {
        printf("插入成功!\n");
    }
    
    sqlite3_close(db);
    return 0;
}

这段代码没什么难度。但我得提醒你一句:千万别在循环里这么干。我曾经见过一个新手,在 for 循环里调了 10 万次 sqlite3_exec,结果程序跑了快 10 分钟还没结束。为什么?因为每次 sqlite3_exec 都会隐式地开启和提交一次事务,这个开销非常大。

⚠️ 注意: 单行插入适合测试或临时操作。生产环境批量写入时,千万别一条一条 exec。

5.2 多行插入:一条 SQL 搞定多条数据

SQLite 支持一条 SQL 插入多行数据。语法很简单:

INSERT INTO users(id, name, age) VALUES
    (1, '张三', 25),
    (2, '李四', 30),
    (3, '王五', 28);

用 C 代码执行的话,跟单行插入一样,只是 SQL 字符串变长了。我个人习惯把这种多行 SQL 放在一个字符串里,然后用 sqlite3_exec 一次性执行。这样比循环调用快得多。

但这里有个坑:SQL 语句的长度有限制。SQLite 默认的 SQL 语句最大长度是 1,000,000 字节(约 1MB)。如果你一次插入几万行,SQL 字符串可能超长。我曾经在项目中就遇到过这个问题,插入 5 万行数据时,程序直接报错返回了。

💡 小技巧: 多行插入适合几百到几千条数据。如果数据量更大,建议用参数化绑定。

5.3 参数化绑定:安全又高效

参数化绑定,说白了就是用 ? 占位符代替具体的值,然后通过 sqlite3_bind_* 系列函数把值传进去。这样做有两个好处:

  • 防止 SQL 注入——用户输入的数据不会拼接到 SQL 里,安全性大大提高
  • 性能更好——SQL 语句只需要编译一次,后面重复使用

来看代码:

#include <sqlite3.h>
#include <stdio.h>
#include <string.h>

int main() {
    sqlite3 *db;
    sqlite3_stmt *stmt;
    char *err_msg = 0;
    
    sqlite3_open("test.db", &db);
    
    // 准备 SQL 语句,用 ? 做占位符
    char *sql = "INSERT INTO users(id, name, age) VALUES(?, ?, ?);";
    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "准备语句失败: %s\n", sqlite3_errmsg(db));
        return 1;
    }
    
    // 绑定参数
    sqlite3_bind_int(stmt, 1, 1);       // 第一个 ? 绑定整数
    sqlite3_bind_text(stmt, 2, "张三", -1, SQLITE_STATIC);  // 第二个 ? 绑定字符串
    sqlite3_bind_int(stmt, 3, 25);      // 第三个 ? 绑定整数
    
    // 执行
    rc = sqlite3_step(stmt);
    if (rc == SQLITE_DONE) {
        printf("插入成功!\n");
    } else {
        fprintf(stderr, "执行失败: %s\n", sqlite3_errmsg(db));
    }
    
    // 重置语句,准备下一次使用
    sqlite3_reset(stmt);
    
    // 清理
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return 0;
}

这里有几个细节要注意:

  • sqlite3_bind_* 的第二个参数是占位符的索引,从 1 开始
  • 字符串绑定用 sqlite3_bind_text,最后一个参数 SQLITE_STATIC 表示字符串指针在语句执行期间有效
  • 每次执行完要调用 sqlite3_reset 重置,才能再次绑定新值
🔑 核心要点: 参数化绑定是生产环境的首选。既安全又高效,而且代码结构清晰。

5.4 批量插入性能优化:事务 + 参数化绑定

好了,终于到了重头戏。如果你需要插入大量数据(比如几万、几十万条),怎么做才最快?

答案就两个关键词:事务 + 参数化绑定

为什么?因为每次插入都自动提交事务,会产生大量的磁盘 I/O。而手动控制事务,可以把多次插入合并成一次提交,性能提升几十倍甚至上百倍。

来看优化后的代码:

#include <sqlite3.h>
#include <stdio.h>
#include <time.h>

int batch_insert(sqlite3 *db, int count) {
    sqlite3_stmt *stmt;
    char *sql = "INSERT INTO users(id, name, age) VALUES(?, ?, ?);";
    
    // 准备语句
    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
    if (rc != SQLITE_OK) return -1;
    
    // 开启事务
    sqlite3_exec(db, "BEGIN TRANSACTION;", 0, 0, 0);
    
    clock_t start = clock();
    
    for (int i = 0; i < count; i++) {
        // 绑定参数
        sqlite3_bind_int(stmt, 1, i);
        sqlite3_bind_text(stmt, 2, "用户", -1, SQLITE_STATIC);
        sqlite3_bind_int(stmt, 3, 20 + (i % 30));
        
        // 执行
        rc = sqlite3_step(stmt);
        if (rc != SQLITE_DONE) {
            fprintf(stderr, "插入失败: %s\n", sqlite3_errmsg(db));
            sqlite3_finalize(stmt);
            return -1;
        }
        
        // 重置,准备下一次绑定
        sqlite3_reset(stmt);
    }
    
    // 提交事务
    sqlite3_exec(db, "COMMIT;", 0, 0, 0);
    
    clock_t end = clock();
    double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
    printf("插入 %d 条数据耗时: %.3f 秒\n", count, time_spent);
    
    sqlite3_finalize(stmt);
    return 0;
}

这段代码我实际测试过。插入 10 万条数据,用事务 + 参数化绑定只需要 0.5 秒左右。而如果不用事务,一条一条插入,可能要 30 秒以上。差距就是这么大。

💡 性能对比:
插入方式 10万条耗时 特点
单条 exec(无事务) ~30秒 最慢,不推荐
多行 SQL(1000行/条) ~2秒 适合中等数据量
参数化绑定 + 事务 ~0.5秒 最快,推荐

5.5 核心知识体系

下面这张图,是我梳理的插入数据核心逻辑。你看一眼就能明白各种方式的定位和适用场景:

SQLite 插入数据核心逻辑 INSERT 插入数据 单行插入 适用:测试、临时操作 缺点:性能最差 多行插入 适用:几百~几千条 注意:SQL长度限制 参数化绑定 适用:生产环境首选 优点:安全+高效 最佳实践:参数化绑定 + 事务 性能排序:参数化绑定+事务 > 多行插入 > 单行插入

5.6 避坑指南

最后,分享几个我踩过的坑,你遇到了能少走弯路:

  • 忘记 reset——参数化绑定后,每次执行完必须调用 sqlite3_reset,否则第二次绑定会失败。我曾经因为这个 bug 排查了整整一下午。
  • 事务太大——一个事务里插入几十万条数据,虽然快,但如果中间出错,整个事务都会回滚。我建议每 1 万条提交一次,既保证性能,又降低风险。
  • 字符串绑定用 SQLITE_TRANSIENT——如果绑定的字符串是临时变量,记得用 SQLITE_TRANSIENT 而不是 SQLITE_STATIC,否则数据可能被覆盖。
  • 主键冲突——插入前先检查主键是否已存在,或者用 INSERT OR REPLACE 处理冲突。我见过有人没做检查,结果数据被静默覆盖了。

好了,插入数据这块就讲到这里。记住一句话:能用参数化绑定就别拼 SQL,能用事务就别一条一条提交。这是我在无数项目中总结出来的铁律。


公众号:蓝海资料掘金营,微信 deep3321