Go 的 database/sql 包本身不解析 SQL 占位符,而是由注册的数据库驱动(如 go-sql-driver/mysql)负责将 ? 替换为安全转义后的参数值;关键前提是必须通过 sql.Open("mysql", connString) 显式指定驱动名,否则占位符不会被识别和展开。
go 的 `database/sql` 包本身不解析 sql 占位符,而是由注册的数据库驱动(如 `go-sql-driver/mysql`)负责将 `?` 替换为安全转义后的参数值;关键前提是必须通过 `sql.open("mysql", connstring)` 显式指定驱动名,否则占位符不会被识别和展开。
在 Go 中使用 MySQL 执行带参数的 SQL 查询时,? 占位符未被替换,通常不是语法或逻辑错误,而是驱动未被正确加载或未被 sql.Open 显式选用。database/sql 是一个抽象接口层,它不处理占位符展开、类型转换或 SQL 注入防护——这些职责完全由底层驱动实现(例如 github.com/go-sql-driver/mysql)。因此,即使你已导入 _ "github.com/go-sql-driver/mysql",若 sql.Open 的第一个参数不是 "mysql",Go 将无法匹配到对应驱动,导致 ? 被原样传入(甚至可能触发 MySQL 服务端语法错误)。
✅ 正确做法如下:
- 确保驱动导入并启用:使用空白导入 _ "github.com/go-sql-driver/mysql",使驱动的 init() 函数注册自身;
- 显式指定驱动名称:调用 sql.Open("mysql", connString),而非 sql.Open("tcp", ...) 或其他任意字符串;
- 验证连接可用性:始终调用 db.Ping() 检查连接池是否就绪;
- 正确使用 Query / Exec:占位符数量必须与参数个数严格一致,类型由驱动自动推导(如 int, string, []byte, nil 等均支持)。
示例代码(含错误处理与资源管理):
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" // 驱动注册
)
func connectToMySQL(user, password, host string, port int, database string) (*sql.DB, error) {
connString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=UTC",
user, password, host, port, database)
db, err := sql.Open("mysql", connString) // ⚠️ 必须是 "mysql"
if err != nil {
return nil, fmt.Errorf("failed to open DB: %w", err)
}
if err = db.Ping(); err != nil { // 测试连接池连通性
db.Close()
return nil, fmt.Errorf("failed to ping DB: %w", err)
}
return db, nil
}
func queryWithPlaceholder(db *sql.DB, barValue string) error {
rows, err := db.Query("SELECT * FROM foo WHERE bar = ?", barValue)
if err != nil {
return fmt.Errorf("query failed: %w", err)
}
defer rows.Close() // ✅ 必须关闭 rows,避免连接泄漏
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
return fmt.Errorf("scan failed: %w", err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
return rows.Err() // 检查迭代结束时的潜在错误
}
⚠️ 注意事项:
- 不要省略 defer rows.Close() 或 stmt.Close(),否则会导致连接池耗尽;
- Query 用于返回结果集(如 SELECT),Exec 用于无结果操作(如 INSERT/UPDATE/DELETE);
- MySQL 驱动不支持命名参数(如 :name 或 $1),仅支持 ? 位置占位符;
- 若仍报错 Error 1064: You have an error in your SQL syntax,大概率是驱动未生效(检查 sql.Open 第一个参数)或连接串格式错误(如缺少 tcp(...) 地址封装);
- 建议开启 parseTime=true 和 loc=UTC 参数,以获得更可控的时间类型行为。
总结:占位符 ? 的展开是 MySQL 驱动的职责,而 sql.Open("mysql", ...) 是激活该职责的“开关”。只要驱动注册正确、sql.Open 指定准确、连接有效,Go 就能安全、高效地执行参数化查询,彻底规避 SQL 注入风险。











