Prepare语句能防SQL注入,因其将SQL模板与参数彻底分离:数据库先预编译带占位符的语句,后续仅传入参数值,参数始终被当作字符串字面量处理;但所有用户输入必须走参数绑定,不可拼接进SQL。

Prepare 语句为什么能防 SQL 注入
因为 Prepare 把 SQL 模板和参数彻底分离:数据库驱动先将带占位符(如 ? 或 $1)的语句发给数据库预编译,后续调用 Exec 或 Query 时只传入参数值,数据库不会把参数当 SQL 解析。哪怕参数是 "'; DROP TABLE users; --",它也只会被当作字符串字面量处理。
关键前提是:**所有用户输入都必须走参数绑定,不能拼接进 SQL 字符串**。
- 错误做法:
"SELECT * FROM users WHERE name = '" + username + "'" - 正确做法:
"SELECT * FROM users WHERE name = ?"+stmt.Query(username)
不同数据库驱动的占位符写法差异
Go 的 database/sql 接口本身不定义占位符语法,具体由驱动实现。常见驱动用法如下:
- MySQL(
github.com/go-sql-driver/mysql):用?,顺序绑定,如INSERT INTO t VALUES (?, ?) - PostgreSQL(
github.com/lib/pq):用$1,$2,位置编号绑定,如INSERT INTO t VALUES ($1, $2) - SQLite(
github.com/mattn/go-sqlite3):支持?、?NNN、:name、@name,但推荐统一用?
混用驱动时别硬编码占位符——比如用 PostgreSQL 驱动却写 ?,会报错 pq: syntax error at or near "?"。
Prepare 后忘记 Close 会导致连接泄漏
sql.Stmt 是有状态的对象,内部持有连接资源。如果只 Prepare 不 Close,在高并发下可能耗尽连接池,表现为 sql: connection refused 或长时间阻塞。
- 必须配对使用:
defer stmt.Close(),尤其在函数内多次调用时 - 不要在循环里反复
Prepare同一语句——应复用*sql.Stmt,它本身是并发安全的 - 若语句只用一次,可直接用
db.Query/Exec,它们内部会自动Prepare+Close
示例正确用法:
stmt, err := db.Prepare("SELECT name FROM users WHERE id = ?")
if err != nil {
log.Fatal(err)
}
defer stmt.Close() // 这行不能少
rows, err := stmt.Query(123)
动态列名或表名无法用参数绑定
Prepare 的参数只能替代**值(value)**,不能替代标识符(identifier),比如表名、列名、ORDER BY 字段、WHERE 条件字段名等。试图这样写会报错:
- ❌ 错误:
"SELECT * FROM ? WHERE ? = ?"—— 数据库不接受占位符用于表名或列名 - ✅ 正确:对这类动态部分,必须白名单校验 + 手动转义(如正则匹配
^[a-zA-Z_][a-zA-Z0-9_]*$),再拼入 SQL
例如过滤排序字段:
allowedSort := map[string]bool{"id": true, "name": true, "created_at": true}
if !allowedSort[sortField] {
http.Error(w, "invalid sort field", http.StatusBadRequest)
return
}
query := "SELECT * FROM users ORDER BY " + sortField + " LIMIT ?"
rows, _ := db.Query(query, limit)
这种场景下,防注入靠的是严格校验,不是 Prepare。


















