Go 1.22 中用 sqlx 防 SQL 注入的核心是:用户可控值必须参数化,结构部分(表名、字段名、ORDER BY)须白名单校验;命名参数需用 NamedQuery 或 Rebind 转换;IN 子句须用 sqlx.In 并二次 Rebind;空 slice 和 NULL 字段需单独处理。

Go 1.22 中用 sqlx 防 SQL 注入,核心就一条:所有用户可控的值必须走参数化,结构部分(表名、字段名、ORDER BY)必须白名单校验。别信“过滤字符串”或“转义单引号”,那根本没用。
命名参数查询必须配 NamedQuery 或 Rebind
sqlx.NamedQuery 支持 :name 这类命名占位符,但底层驱动(如 MySQL 用 ?,PostgreSQL 用 $1)不认它——直接传给 db.Query 会 panic:sql: expected 0 arguments, got 1。
- 正确流程是:构造带
:key的 SQL 字符串 → 用map[string]interface{}或 struct 填充参数 → 调db.NamedQuery(query, args) - 若你手动拼了 SQL 再想用命名参数,得先调
db.Rebind(query)把:name转成目标驱动格式,再传给db.Query - 别混用:
db.Query("SELECT * FROM users WHERE name = :name", args)会失败;db.NamedQuery才处理命名参数
IN 子句不能直接传 slice,必须用 sqlx.In
写 WHERE id IN :ids 并把 []int{1,2,3} 塞进 args["ids"],NamedQuery 会把它当一个整体绑定,结果查不到数据或报语法错。
- 唯一可靠方式是
sqlx.In:它返回问号占位符字符串和展开后的参数 slice - 必须再过一遍
db.Rebind:比如sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)返回"IN (?, ?, ?)"和[]interface{}{1,2,3},但 MySQL 驱动要?,PostgreSQL 要$1,$2,$3,所以得db.Rebind(query) - 空 slice 要单独判断:
len(ids) == 0时不能执行查询,否则IN ()是语法错误
表名、排序字段、方向必须白名单校验后拼接
ORDER BY、FROM、GROUP BY 这些子句数据库根本不支持运行时参数化。写 db.Query("SELECT * FROM ? ORDER BY ?", table, field) 必然 panic。
- 排序字段只能从预设 map 中取:
validFields := map[string]bool{"created_at": true, "score": true, "name": true},不在里面就拒掉 - 排序方向也得白名单:
if dir != "ASC" && dir != "DESC"就直接返回错误,别信strings.ToUpper(dir)后拼接 - 分表场景用 switch 或常量映射:
switch tenant { case "prod": table = "users_prod" },别写"users_" + tenant
结构体扫描要显式声明 db tag 且处理 NULL
sqlx.StructScan 不防注入,但它错配会导致字段静默为空、权限逻辑误判——这虽不是注入,但可能被利用绕过业务检查。
- 所有可能为 NULL 的字段,必须用
sql.NullString、sql.NullInt64或指针类型(如*string),否则遇到 NULL 会 panic:sql: Scan error on column index 0: unsupported Scan, storing driver.Value typeinto type *string - 字段名默认按数据库列名匹配(区分大小写),不是 SQL 别名。写
SELECT id, name AS username,结构体就得有Username string `db:"username"` - 字段必须导出(首字母大写),否则反射写不进去,扫描后值始终为空,不报错但逻辑失效
最易被忽略的是:白名单校验和 sqlx.In 处理都得覆盖边界情况——比如空 slice、空字符串排序字段、NULL 字段类型不一致。这些地方一漏,安全链就断了。










