必须用sqlite3_prepare_v2+sqlite3_bind_*组合,因字符串拼接会将用户输入直接混入sql语法流,导致引号失衡或逻辑篡改;转义等手段易被绕过,而绑定使sqlite仅将参数视为纯字节流,实现本质隔离。

必须用 sqlite3_prepare_v2 + sqlite3_bind_* 组合,不能拼接字符串——这是防注入的唯一有效路径。
为什么不能用字符串拼接SQL
拼接会把用户输入直接塞进SQL语法流里。比如 "O'Reilly" 会让单引号失衡,"admin' OR 1=1--" 会改写整个查询逻辑。转义、替换引号、过滤关键字这些都不可靠:Unicode变体、空字节、嵌套注释都能绕过。SQLite 不解析绑定值,只当它是纯字节流,这才是本质隔离。
怎么调用 sqlite3_prepare_v2 和绑定参数
核心是三步:准备 → 绑定 → 执行。占位符只支持 ?(位置)或 @name / :name(命名),不能混用,也不能用于表名或列名。
-
sqlite3_prepare_v2返回SQLITE_OK才能继续;否则用sqlite3_errmsg(db)查错,常见原因是 SQL 语法错误,不是表不存在 - 绑定前确认
sqlite3_stmt*非空,位置参数从 1 开始:sqlite3_bind_text(stmt, 1, "abc", -1, SQLITE_TRANSIENT) - 命名参数先查索引:
int idx = sqlite3_bind_parameter_index(stmt, "@user"); sqlite3_bind_text(stmt, idx, ...) - 字符串推荐用
SQLITE_TRANSIENT(SQLite 自行拷贝内存),避免原始std::string出作用域后指针失效
容易被忽略的生命周期和复用陷阱
预编译语句句柄 sqlite3_stmt* 是资源,不是普通变量。没 sqlite3_finalize 就泄漏;跨线程复用同一句柄在 MySQL 中非法,在 SQLite 中虽允许但易引发竞态;重用前必须 sqlite3_reset,否则上次绑定仍生效。
更隐蔽的问题是:连接关闭前未 finalize,SQLite 可能返回 SQLITE_BUSY,而你根本没看到这个错误码——它藏在 prepare 失败的分支里,不是执行时抛出的。











