防止SQL注入最有效的方式是使用参数化查询,通过预编译语句实现数据与代码分离,确保用户输入仅作为数据绑定而非SQL指令执行;动态表名、列名等无法参数化部分须严格白名单校验。

只要没拼接字符串,database/sql 本身不会引入注入风险;真正危险的是你亲手把用户输入塞进 SQL 字符串里。
占位符语法必须匹配驱动,混用直接 panic
MySQL 和 SQLite 驱动只认 ?,PostgreSQL 驱动只认 $1、$2。写错会报类似 sql: expected 1 arguments, got 0 或 pq: syntax error at or near "?" 的错误。
-
db.Query("SELECT * FROM users WHERE id = ?", id)—— MySQL/SQLite 安全,PostgreSQL 报错 -
db.Query("SELECT * FROM users WHERE id = $1", id)—— PostgreSQL 安全,MySQL 报错 - 别试图混用:
"WHERE name = ? AND status = $1"必然失败 - SQLite 虽然两种都支持,但统一用
?更省心,避免跨库迁移时出问题
动态表名、ORDER BY 字段这些根本不能参数化
SQL 协议不支持把标识符(如表名、列名、ASC/DESC)当参数传。写 SELECT * FROM ? 或 ORDER BY ? 会直接触发 sql: expected 0 arguments, got 1。
- 表名拼接前必须白名单校验:
validTables := map[string]bool{"users": true, "orders": true} - 排序字段也一样:
if !validSortFields[sortField] { return errors.New("invalid sort field") } - 拼
ORDER BY " + sortField + " " + sortDir可以,但sortDir只能是"ASC"或"DESC",不能靠字符串替换过滤 - 别信正则删单引号或分号——
UnIoN、/**/、Unicode 空格都能绕过
看似安全的“参数化”写法,其实还在拼字符串
占位符不是消毒水,它只保护问号位置上的值;一旦你在 SQL 字符串里做了任何拼接,防护就失效了。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
-
db.Query("SELECT * FROM " + tableName + " WHERE id = ?", id)—— 表名拼接,高危 -
Where("name = ?", name).Where("age > " + ageStr)—— 后半段又回到拼接,ageStr是用户输入就崩 -
db.Raw("SELECT * FROM " + table + " WHERE id = ?", id)—— GORM 里也一样,Raw()不自动校验表名 - 从
map[string]interface{}取值传参前必须断言:v, ok := data["id"].(int); if !ok { /* 拒绝 */ },否则可能退化为字符串拼接
Scan 和日志是两个容易被忽略的“非注入但致命”环节
注入发生在 Query 或 Exec 那一刻,但 Scan 错配类型、日志打原始 SQL,同样会导致线上故障或信息泄露。
-
Scan时字段顺序必须和SELECT严格一致;用结构体接收时,字段名要和列别名对齐,比如SELECT name AS username对应Username string - 可能为
NULL的字段,必须用sql.NullString、sql.NullInt64或指针接收,否则sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string - 日志里别打原始 SQL 字符串:
log.Printf("query: %s, args: %v", sql, args)如果sql是拼出来的,等于把漏洞日志化;应只记录模板,如"SELECT * FROM users WHERE email = ?"
最麻烦的从来不是 WHERE 条件怎么写,而是那些“必须动态”的地方——表名、字段、排序方向。它们没法参数化,只能靠代码兜底:白名单 + 显式映射 + 拒绝未知输入。这点不落实,其他所有参数化都只是纸糊的墙。

















