直接拼接sql字符串危险,因用户输入会被当代码执行,导致sql注入;database/sql仅做占位符替换与类型绑定,不解析sql逻辑,必须用?或命名参数(如$1、:name)并严格校验不可参数化的部分。

为什么直接拼接 SQL 字符串是危险的
因为会触发 SQL 注入,database/sql 本身不解析 SQL,只做占位符替换和类型绑定。如果你写 "SELECT * FROM users WHERE name = '" + name + "'",攻击者传入 name = "admin' OR '1'='1" 就能绕过条件。原生库只信任通过 Query、Exec 等方法传入的参数,且必须用问号(?)或命名占位符(如 $1、:name),具体取决于驱动。
不同数据库驱动的占位符语法差异
Go 的 database/sql 是抽象层,实际占位符由驱动决定:mysql 驱动用 ?,postgres 驱动用 $1、$2,sqlite3 同时支持 ? 和 :name。写死一种语法会导致换库时报错 sql: expected 2 arguments, got 1 或 pq: syntax error at or near "$2"。
- MySQL:
SELECT * FROM users WHERE id = ? AND status = ? - PostgreSQL:
SELECT * FROM users WHERE id = $1 AND status = $2 - SQLite:
SELECT * FROM users WHERE id = ? AND status = ?或WHERE id = :id AND status = :status
Query 和 Exec 的参数传递必须是展开的切片
Query 和 Exec 不接受数组或 map,必须把参数逐个传入,或用 ... 展开切片。常见错误是传了 []interface{}{a, b} 却没加 ...,导致函数收到一个参数(切片本身),而非两个独立参数。
正确写法:
args := []interface{}{123, "active"}
rows, err := db.Query("SELECT name FROM users WHERE id = ? AND status = ?", args...)
错误写法(会报错 sql: expected 2 arguments, got 1):
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
db.Query("...", args) // 缺少 ...
注意:所有参数必须是 interface{} 类型,不能直接传 int、string 等具体类型——虽然 Go 会自动装箱,但显式转成 interface{} 更清晰,尤其在处理 nil 时(要用 sql.NullString 等)。
IN 子句无法直接参数化,需要动态生成占位符
这是最容易卡住的地方:WHERE id IN (?, ?, ?) 的问号数量必须和输入元素个数一致,而 IN 参数长度是运行时决定的。不能写 WHERE id IN (?) 然后传入 []int{1,2,3} —— 那只会匹配单个值 [1 2 3],不是三个独立值。
安全做法是预先构建占位符字符串:
ids := []int{1, 2, 3}
placeholders := make([]string, len(ids))
args := make([]interface{}, len(ids))
for i, id := range ids {
placeholders[i] = "?"
args[i] = id
}
query := "SELECT * FROM users WHERE id IN (" + strings.Join(placeholders, ",") + ")"
rows, _ := db.Query(query, args...)
别用字符串拼接 ids 值本身;也别依赖第三方库“自动处理 IN”,底层仍是这一步——只是封装了而已。PostgreSQL 用户可用 UNNEST($1::int[]),但可读性和兼容性不如显式占位符。
真正麻烦的从来不是语法,而是当业务逻辑里混进一个未校验的用户输入字段、又顺手用 fmt.Sprintf 拼进 SQL 时,漏洞就藏进去了。参数化查询不是“写了占位符”就万事大吉,关键是所有外部输入都必须走参数通道,包括表名、列名这种无法参数化的部分——得用白名单严格校验。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










