只要用对占位符、不拼字符串、不碰标识符,database/sql就能防住SQL注入;占位符须严格匹配驱动(MySQL/SQLite用?,PostgreSQL用$1),表名、字段名、ORDER BY等必须白名单校验,Prepare需Close,Scan需处理NULL。

只要用对占位符、不拼字符串、不碰标识符,database/sql 就能防住 SQL 注入——它不靠魔法,只靠你写 SQL 的方式。
占位符必须严格匹配驱动,混用等于裸奔
MySQL/SQLite 驱动只认 ?,PostgreSQL 驱动只认 $1、$2。这不是风格问题,是协议级硬约束,错配后行为不可控。
-
db.Query("SELECT * FROM users WHERE id = ?", 123)✅ 在 MySQL 下安全,在 PostgreSQL 下直接报sql: expected 0 arguments, got 1 -
db.Query("SELECT * FROM users WHERE id = $1", 123)✅ 在 PostgreSQL 下安全,在 MySQL 下报pq: syntax error at or near "?"(注意错误里还带错别字) -
db.Query("SELECT * FROM users WHERE id = ? AND status = $1", 123, "active")❌ 混用,多数驱动会忽略第二个参数,或静默退化成字面量处理
SQLite 虽然两种都支持,但统一用 ? 更省心;跨库迁移时硬编码占位符,测试可能通过,线上却查不到数据或 panic。
表名、字段名、ORDER BY 这些根本不能参数化
SQL 标准规定表名、列名、排序方向属于查询结构,数据库在编译阶段就要确定,没有运行时绑定接口。试图用 ? 或 $1 替代它们,不是报错就是被忽略。
立即学习“go语言免费学习笔记(深入)”;
-
db.Query("SELECT * FROM ?", tableName)→ 必然 panic:sql: expected 0 arguments, got 1 -
db.Query("SELECT * FROM users ORDER BY ?", sortField)→ 多数驱动直接拒绝执行
真正该做的是白名单校验:
- 排序字段:
validSortFields := map[string]bool{"created_at": true, "name": true, "score": true},查不到就拒掉 - 排序方向:
if dir != "ASC" && dir != "DESC" { return err },别信strings.ToUpper(dir)后直接拼 - 表名映射:用
switch或常量,比如tenantID == "prod"→ 查users_prod,而不是"users_" + tenantID
GORM 的 Raw() 和 Where() 不是免死金牌
GORM 默认走参数化,但一旦你混进字符串拼接,整条链就失效了。它不拦截你塞进去的拼接字符串,只保证占位符部分被转义。
-
db.Raw("SELECT * FROM users WHERE name = ?", name)✅ 安全 -
db.Raw(fmt.Sprintf("SELECT * FROM %s", tableName))❌ 裸奔,tableName是用户输入就崩 -
db.Where("name = ?", name).Where("age > " + ageStr)❌ 后半段又回到拼接,ageStr是用户输入就破防 -
db.Where("name = '" + name + "'")❌ 典型崩溃点,等同于裸拼
从 map[string]interface{} 取值传参前必须断言:v, ok := data["id"].(int); if !ok { /* 拒绝 */ },否则可能退化为字符串拼接。
Prepare 必须配对 Close,否则连接泄漏
*sql.Stmt 是有状态对象,内部持有连接资源。不 Close() 就等于把连接池里的“座位”一直占着。
- 高并发下易触发
sql: connection refused或长时间阻塞 - 必须写
defer stmt.Close(),尤其函数内多次调用时 - 别在循环里反复
Prepare同一语句——复用*sql.Stmt,它本身并发安全 - 如果只查一次,直接用
db.Query更省心,它内部会自动Prepare+Close
最常被忽略的其实是 Scan:字段可能为 NULL 却用普通 string 接收 → panic: sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string</nil>。所有可能为 NULL 的字段,必须用 sql.NullString、sql.NullInt64 等类型接收。


















