常见原因是语句末尾多余分号导致截断、连接未开启事务、路径不可写或表结构不匹配;sqlite3_exec对insert不触发回调,仅select有结果时才调用,且需确保sql正确、回调签名严格符合int callback(void*, int, char, char)并返回0。

用 sqlite3_exec 执行 INSERT 语句时,为什么没报错却没写入?
常见原因是数据库连接未开启事务,或语句末尾多了分号导致解析失败。SQLite 的 sqlite3_exec 默认以单语句模式执行,遇到分号会截断——比如写成 "INSERT INTO users VALUES (1, 'Alice');",实际只执行了 INSERT INTO users VALUES (1, 'Alice') 前半部分(分号后被忽略),但更关键的是:它不返回错误码,容易误判成功。
实操建议:
- 去掉 SQL 字符串末尾的分号,只传纯语句:
"INSERT INTO users VALUES (1, 'Alice')" - 确保
sqlite3_open成功返回SQLITE_OK,且路径是可写的绝对路径或相对路径(注意工作目录) - 检查表是否存在、字段类型是否匹配——例如向 INTEGER 字段插入字符串不会报错,但值变成 0
- 如需确认影响行数,改用
sqlite3_prepare_v2+sqlite3_step+sqlite3_changes组合
用 sqlite3_exec 带回调查询数据,为什么回调函数从不触发?
根本原因:该函数只在 SELECT 有结果集时才调用回调;如果查询无结果(比如 WHERE 不匹配)、语法错误、或用了非查询语句(如 INSERT),回调压根不会执行,也不会报错——这是最易踩的坑。
实操建议:
- 确认 SQL 确实是 SELECT,且表中有匹配数据(先用 CLI 验证:
sqlite3 test.db "SELECT * FROM users;") - 回调函数签名必须严格为:
int callback(void*, int, char**, char**),且返回 0;返回非 0 会中断查询 - 第 3 个参数
char**是字段值数组,但 SQLite 返回的是 C 字符串指针,它们生命周期仅在回调内有效,不能存指针长期使用 - 若只需取一行一列,用
sqlite3_get_table更省心(它把全部结果拷贝到堆内存)
用 sqlite3_prepare_v2 绑定参数插入,为什么绑定 SQLITE_TRANSIENT 后程序崩溃?
不是绑定方式错了,而是你传给 sqlite3_bind_text 的字符串指针,在 sqlite3_step 执行前已被释放——SQLITE_TRANSIENT 表示“SQLite 自己复制字符串”,但前提是源内存当时还有效。
实操建议:
- 绑定前确保字符串变量作用域覆盖整个 prepare → step → finalize 流程,比如不要绑定局部
std::string的c_str(),而应先const std::string& s = ...; sqlite3_bind_text(stmt, 1, s.c_str(), -1, SQLITE_STATIC); - 对临时字符串,用
SQLITE_STATIC更安全(你保证内存不释放),或改用SQLITE_DETERMINISTIC(仅限常量字符串) - 每次
sqlite3_step后必须检查返回值:SQLITE_DONE表示成功,SQLITE_ROW表示查询有数据,SQLITE_ERROR才要查sqlite3_errmsg - 别漏掉
sqlite3_finalize,否则预处理语句泄漏,反复 prepare 会耗尽资源
查询结果中如何安全读取 BLOB 或 NULL 字段?
sqlite3_column_type 是唯一可靠判断依据。直接调用 sqlite3_column_text 或 sqlite3_column_blob 读 NULL 字段会返回 nullptr,但不会报错;读 BLOB 时若误用 _text 接口,可能因内部未终止而越界访问。
实操建议:
- 先用
sqlite3_column_type(stmt, col_idx)判断类型:SQLITE_NULL、SQLITE_TEXT、SQLITE_BLOB、SQLITE_INTEGER等 - 读 BLOB 必须配对使用:
sqlite3_column_blob+sqlite3_column_bytes,后者返回真实字节数,不能依赖 strlen - 读 TEXT 也建议用
sqlite3_column_bytes获取长度,避免源数据含嵌入 \0 导致截断 - NULL 字段所有
sqlite3_column_*函数都返回 0 / nullptr,必须显式检查类型再分支处理
C++ 调用 SQLite 最容易被忽略的,是错误检查粒度和内存生命周期的耦合——不是“调用成功就完事”,而是每一步(open、prepare、bind、step、column、finalize)都可能失败,且每个失败点需要不同的恢复动作。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











