database/sql的Query和Exec天然具备参数化能力,因标准库设计强制使用占位符(如?)配合参数传入,由驱动转换为对应数据库的预处理语法,从而默认防御SQL注入。

为什么database/sql的Query和Exec自带参数化能力
Go标准库database/sql从设计上就不支持拼接SQL字符串执行,所有查询都必须通过占位符+参数方式传入值。这不是“需要额外配置”,而是底层驱动强制要求——比如mysql驱动会把?占位符转为MySQL原生预处理语句,postgres驱动则对应$1、$2等。只要你不手动用fmt.Sprintf或+拼接SQL,就天然免疫基础SQL注入。
Query和Exec中占位符怎么写、参数怎么传
不同数据库驱动占位符语法不一致,但Go的database/sql接口统一用?,由驱动内部转换。你只需按顺序传参,不要自己写$1或:name:
// ✅ 正确:用?,参数按顺序传
rows, err := db.Query("SELECT name FROM users WHERE age > ? AND status = ?", 18, "active")
<p>// ❌ 错误:手动拼接字符串
sql := "SELECT name FROM users WHERE age > " + strconv.Itoa(age) // 危险!
db.Query(sql)</p><p>// ❌ 错误:混用占位符(如在MySQL里写$1)
db.Query("SELECT * FROM users WHERE id = $1", 123) // 驱动报错或忽略- MySQL驱动(
github.com/go-sql-driver/mysql)只认? - PostgreSQL驱动(
github.com/lib/pq)也接受?,内部转成$1;但若直接写$1,会因SQL解析失败而报pq: syntax error at or near "$" - SQLite驱动(
github.com/mattn/go-sqlite3)同理,只应使用?
遇到sql.ErrNoRows或类型错误时,别改用字符串拼接
常见错误场景:动态列名、表名、ORDER BY字段无法用参数化,有人会退回去拼SQL。这是高危操作,必须用白名单校验代替:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
// ❌ 危险:拼接列名
sortCol := r.URL.Query().Get("sort")
db.Query("SELECT * FROM posts ORDER BY " + sortCol + " DESC")
<p>// ✅ 安全:白名单映射
validSortCols := map[string]bool{"title": true, "created_at": true, "id": true}
if !validSortCols[sortCol] {
http.Error(w, "invalid sort column", http.StatusBadRequest)
return
}
db.Query("SELECT * FROM posts ORDER BY ? DESC", sortCol) // 注意:ORDER BY后不能参数化!这里只是示意校验逻辑- 列名、表名、关键字(如
ASC/DESC)无法被参数化,SQL标准不允许 - 必须用预定义白名单做严格校验,不能靠正则过滤或trim空格
- 如果业务真要支持任意字段排序,考虑生成完整SQL模板再校验,而不是运行时拼接
用Scan接收结果时,注意类型匹配和NULL处理
参数化本身防注入,但Scan阶段出错可能掩盖真实问题。比如数据库字段为TEXT但代码用*string接收,遇到NULL会panic:
// ❌ 可能panic:当bio为NULL时
var bio string
err := row.Scan(&bio) // sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string
<p>// ✅ 安全:用sql.NullString或其他null-safe类型
var bio sql.NullString
err := row.Scan(&bio)
if err == nil && bio.Valid {
fmt.Println(bio.String)
}-
Scan失败不会导致SQL注入,但可能让错误日志暴露数据库结构,或引发服务崩溃 - 对可能为NULL的字段,优先用
sql.NullString、sql.NullInt64等类型 - 如果用结构体扫描,确保字段类型与数据库类型兼容,避免隐式转换失败
真正危险的从来不是“会不会参数化”,而是以为用了?就万事大吉,然后在列名、权限控制、错误处理这些环节松懈。参数化只管值,不管结构——那部分得靠代码逻辑兜底。

















