go 的 database/sql 默认自动预编译,只需正确使用占位符(mysql 用 ?,postgresql 用 $1)并传入参数切片;禁止拼接 sql 字符串、动态标识符(表名/字段名)或类型不匹配参数,否则会静默退化为字符串替换导致注入风险。

Go 的 database/sql 默认就用预编译,但得写对方式
Go 标准库的 database/sql 在调用 Query、Exec 等方法时,只要传入占位符(? 或 $1)和参数切片,底层驱动(如 lib/pq、go-sql-driver/mysql)会自动走预编译路径——前提是数据库支持且连接没禁用。不是“要手动开启预编译”,而是“别手抖拼 SQL”。
常见错误现象:sql: converting argument $1 type: unsupported type string, a string(参数没传对)、ERROR: syntax error at or near "123"(字符串被直接拼进 SQL)、查询结果为空但无报错(类型不匹配导致预编译失败后退化为文本替换)。
- MySQL 驱动用
?占位,PostgreSQL 用$1、$2,别混用 - 参数必须是独立变量或切片元素,不能是字符串拼接结果:
"'"+name+"'"是雷区 - 如果用
fmt.Sprintf拼接 SQL,哪怕只拼表名或字段名,也等于放弃预编译保护 - 预编译语句在连接池中可复用,但驱动通常按 SQL 字符串缓存,所以带不同占位符数量的语句会被视为不同语句
哪些情况预编译会静默失效
预编译不是银弹,有些写法会让驱动回退到字符串替换,且不报错。最典型的是动态字段或表名——SQL 占位符只能用于值,不能用于标识符(identifier)。
使用场景:分表查询、按用户 ID 路由到不同表、动态排序字段(ORDER BY ?)。
-
SELECT * FROM user_? WHERE id = ?→ 错!表名不能用?,驱动会报错或忽略 -
SELECT * FROM users ORDER BY ?→ 错!排序字段也不支持占位符,多数驱动直接报错pq: syntax error at or near "$1" - 解决办法只有白名单校验 + 字符串拼接,比如
map[string]bool{"created_at": true, "name": true},再拼进 SQL - 某些驱动(如 SQLite)对
WHERE IN (?)不支持多值展开,需动态生成IN ($1, $2, $3)并传对应参数
sql.Stmt 手动预编译适合什么场景
标准库的 db.Query("SELECT ...", args...) 是“自动预编译 + 自动 Close”,够大多数场景;但高频执行、参数类型固定、对延迟敏感时,该用 db.Prepare 得到 *sql.Stmt。
性能影响:单次查询几乎无差别;万级 QPS 下,复用 *sql.Stmt 可省掉每次解析+计划生成开销(尤其 PostgreSQL)。但注意它绑定到单个连接,连接池切换可能让缓存失效。
- 必须调用
stmt.Close(),否则连接泄漏(Go 1.19+ 对未关闭 stmt 有警告,但不 panic) - 不能跨 goroutine 复用
*sql.Stmt,它非并发安全;应每个 goroutine 自己Prepare或用db.Query - PostgreSQL 中,长时间空闲的
PREPARE语句可能被服务端清理,再次EXECUTE时触发重新准备(透明,但有毫秒级延迟) - 示例:
stmt, _ := db.Prepare("SELECT name FROM users WHERE id = ?"); defer stmt.Close(); stmt.QueryRow(123)
真正防注入的关键不在预编译,而在参数类型和边界
预编译只是手段,核心是让数据库把输入当“值”而非“语法”。但如果传入的参数本身类型不对(比如把整数 ID 当字符串传),或业务逻辑没做长度/格式校验,照样可能出问题。
- 数字型参数别用
string接再转int,直接传int64,避免"123; DROP TABLE users"这种字符串绕过 - 字符串参数务必限制长度(如用户名 ≤ 32 字符),超长直接拒掉,防止缓冲区类攻击或日志污染
- JSON 字段、HTML 内容等富文本,入库前应做最小化清洗(如
html.EscapeString),预编译只管 SQL 层,不管渲染层 - PostgreSQL 的
jsonb参数若用json.RawMessage直接传,要注意内部是否含恶意键名(如"__proto__"),这不属于 SQL 注入,但属于数据污染
预编译能拦住 95% 的注入,剩下那 5% 往往卡在标识符拼接、类型擦除、或前端传来的原始 JSON 未经清洗就入库——这些地方没有 ? 可站,得靠代码里一行行盯。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











