go的lib/pq驱动必须使用$1、$2等位置参数,因其底层依赖postgresql原生协议,不识别%s或?等占位符;误用会导致语法错误或sql注入。

直接结论:用 $1、$2 这类位置参数,别碰 %s 或字符串拼接 —— pq 库不认 Python 风格的占位符,硬写会报错或被绕过。
为什么 pq 库必须用 $n 而不是 %s
pq 是 Go 语言的 PostgreSQL 驱动,它底层直接对接 libpq,而 libpq 的协议只接受 PostgreSQL 原生的参数化语法:$1、$2…。如果你在查询字符串里写 %s,pq 不会帮你替换,而是原样发给数据库,结果要么是语法错误(如 ERROR: syntax error at or near "%"),要么变成字面量被当成普通字符串处理,完全失去参数化意义。
常见错误现象:
- 执行
db.Query("SELECT * FROM users WHERE name = %s", username)→ 报错pq: syntax error at or near "%" - 改用
fmt.Sprintf拼接"... WHERE name = '" + username + "'"→ 看似能跑,但遇到O'Reilly就崩,SQL 注入立刻生效
正确写法:用 $1 占位 + db.Query 或 db.QueryRow 传参
Go 的 pq 驱动要求 SQL 字符串中用 $1、$2 表示参数位置,实际值通过函数参数顺序传递,类型自动匹配。
实操建议:
- 所有用户输入都必须进参数列表,不能出现在 SQL 字符串里
- 参数数量和
$n编号必须严格一致,$1后不能跳$3 - WHERE 条件多个字段时,按顺序编号,比如
WHERE status = $1 AND created_at > $2 - INSERT 多值场景也一样:
INSERT INTO logs (msg, level) VALUES ($1, $2)
示例:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
rows, err := db.Query("SELECT id, name FROM users WHERE email = $1 AND active = $2", userEmail, true)
if err != nil {
// 处理错误
}
defer rows.Close()
哪些场景容易漏掉参数化?
动态 WHERE 条件、ORDER BY、LIMIT/OFFSET、IN 列表,这些地方最容易手滑拼字符串。
关键判断点:
-
ORDER BY字段名不能参数化($1会被当作文本值而非列名),必须白名单校验后拼接,比如if col == "created_at" || col == "name" { query += " ORDER BY " + col } -
LIMIT $1 OFFSET $2安全,但LIMIT 10写死没问题;若需动态,务必确保传入的是整数类型,避免传入"10; DROP TABLE users" -
IN ($1, $2, $3)只能用于固定个数,变长列表要用pq.Array或构建动态占位符串(如strings.Repeat("$%d,", len(ids)-1) + "$%d")
别信“转义一下就安全”的说法
PostgreSQL 提供 quote_literal() 或 Go 里的 pq.QuoteLiteral(),但它们只是加单引号+转义单引号,不能替代参数化。一旦你用 quote_literal() 拼进 SQL,就等于又回到了字符串拼接的老路 —— 类型不检查、边界难控制、嵌套易出错。
真正安全的底线只有一条:用户数据永远不接触 SQL 字符串的任何一部分。只要看到 +、fmt.Sprintf、strings.Join 出现在 SQL 字符串生成逻辑里,就要立刻停下来重写。
最常被忽略的细节是:哪怕只有一处用了拼接,整个查询链就失效了。防御不是“大部分用了参数化”,而是“每一处都必须用”。










