
本文详解 Go 语言中使用 database/sql 操作 MySQL 时,因 Scan() 参数数量与查询字段不匹配导致的常见错误,并提供修复方案、最佳实践及完整可运行示例。
本文详解 go 语言中使用 `database/sql` 操作 mysql 时,因 `scan()` 参数数量与查询字段不匹配导致的常见错误,并提供修复方案、最佳实践及完整可运行示例。
在 Go 中通过 sql.QueryRow 查询数据库时,一个极易被忽视却高频出错的问题是:Scan() 方法传入的变量数量必须严格等于 SQL 查询返回的列数。你提供的代码中执行了 SELECT title, date, url FROM ...(共 3 列),却只传入单个 &str 变量进行扫描:
err = db.QueryRow("SELECT title, date, url FROM datesheet_ipu ORDER BY id DESC LIMIT 1").Scan(&str)
这将触发 sql.ErrNoRows 或更常见的 sql: expected 1 destination argument, got 3 类似错误(具体提示取决于驱动版本),因为 Scan() 无法将三列值映射到一个字符串变量。
✅ 正确做法是:为每一列声明对应类型的变量,并按顺序传入 Scan():
var title, date, url string
err := db.QueryRow(
"SELECT title, date, url FROM datesheet_ipu ORDER BY id DESC LIMIT 1",
).Scan(&title, &date, &url)
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("no rows found")
} else {
fmt.Printf("query error: %v\n", err)
}
return false
}
fmt.Printf("Title: %s, Date: %s, URL: %s\n", title, date, url)
⚠️ 关键注意事项:
- ✅ 类型必须匹配:
title和url通常为string,而date若是DATE/DATETIME类型,推荐使用time.Time(需导入time包并确保 MySQL 连接参数含parseTime=true); - ✅ 添加连接参数:在 DSN 中启用时间解析,避免
date列扫描失败:db, err = sql.Open("mysql", "root:admin@/ipuscraper?parseTime=true") - ❌ 移除
defer db.Close():initDatabase()是初始化函数,defer在函数退出时立即关闭连接池,导致后续所有数据库操作失效。*sql.DB是连接池句柄,应全局复用并在程序退出时关闭(如defer db.Close()放在main()函数末尾); - ✅ 始终检查
err:QueryRow().Scan()失败可能因无数据(sql.ErrNoRows)、类型不匹配、空值(NULL)未处理等,不可忽略; - ✅ 使用结构体提升可维护性(进阶推荐):
type DateSheet struct { Title string `db:"title"` Date time.Time `db:"date"` URL string `db:"url"` } var ds DateSheet err := db.QueryRow("SELECT title, date, url FROM ...").Scan(&ds.Title, &ds.Date, &ds.URL)
? 完整修正版 initDatabase 示例(含错误处理与连接池管理):
var db *sql.DB
func initDatabase() bool {
var err error
// 启用 parseTime 支持 time.Time 扫描
db, err = sql.Open("mysql", "root:admin@/ipuscraper?parseTime=true")
if err != nil {
fmt.Printf("Failed to open database: %v\n", err)
return false
}
// 验证连接有效性(非 defer!)
if err = db.Ping(); err != nil {
fmt.Printf("Failed to ping database: %v\n", err)
return false
}
// 测试查询:三列 → 三个变量
var title, url string
var date time.Time
err = db.QueryRow(
"SELECT title, date, url FROM datesheet_ipu ORDER BY id DESC LIMIT 1",
).Scan(&title, &date, &url)
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("No data found in table datesheet_ipu")
} else {
fmt.Printf("Query failed: %v\n", err)
}
return false
}
fmt.Printf("Retrieved: Title=%q, Date=%v, URL=%q\n", title, date, url)
return true
}
// 程序退出前关闭连接池(例如在 main() 中调用)
func closeDatabase() {
if db != nil {
db.Close()
}
}
? 总结:Go 的 database/sql 要求“查询列数 = Scan 参数个数 + 类型严格一致”。牢记此原则,配合 parseTime=true、合理生命周期管理及结构化扫描,即可稳健处理 MySQL 查询。










