
本文详解 go 使用 database/sql 查询 mysql 多列时的常见错误与正确实践,重点说明 scan 方法的参数传递方式、错误处理规范及格式化输出技巧。
本文详解 go 使用 database/sql 查询 mysql 多列时的常见错误与正确实践,重点说明 scan 方法的参数传递方式、错误处理规范及格式化输出技巧。
在 Go 中通过 database/sql 包查询 MySQL 多列数据是一个高频操作,但初学者常因误解 rows.Scan() 的调用方式而报错或漏读数据。你提供的代码中,问题核心在于:对同一行记录重复调用 rows.Scan() —— 这会导致第二次调用试图读取下一行的第二列(实际已越界),从而引发 sql.ErrNoRows 或静默失败。
✅ 正确做法是:为每一行调用一次 Scan(),并将所有目标字段地址一次性传入。Scan 方法签名是 Scan(dest ...interface{}) error,其中 ...interface{} 表示可变参数,支持任意数量的指针参数,对应 SQL 查询结果的各列顺序。
以下是修复后的完整示例(含错误检查与资源管理):
func fetchTwoColumns(w http.ResponseWriter, r *http.Request) {
conn, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database")
if err != nil {
http.Error(w, "Database connection failed: "+err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close() // 确保连接最终关闭
statement, err := conn.Prepare("SELECT first, second FROM table")
if err != nil {
http.Error(w, "Prepare statement failed: "+err.Error(), http.StatusInternalServerError)
return
}
defer statement.Close() // 防止 Prepare 泄露
rows, err := statement.Query()
if err != nil {
http.Error(w, "Query execution failed: "+err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close() // 必须关闭 rows,否则连接可能被长期占用
for rows.Next() {
var first, second string
// ✅ 一次性扫描两列,顺序必须与 SELECT 字段严格一致
if err := rows.Scan(&first, &second); err != nil {
http.Error(w, "Scan failed: "+err.Error(), http.StatusInternalServerError)
return
}
// ✅ 使用格式化动词提升可读性与安全性
fmt.Fprintf(w, "Title of first is: %s\nThe second is: %s\n", first, second)
}
// 检查 rows.Next() 循环结束后是否发生扫描错误(如类型不匹配)
if err := rows.Err(); err != nil {
http.Error(w, "Row iteration error: "+err.Error(), http.StatusInternalServerError)
return
}
}? 关键注意事项:
- 变量声明与 Scan 顺序必须严格匹配:SELECT first, second → Scan(&first, &second);若顺序颠倒或类型不兼容(如将 INT 列 Scan 到 string),会触发运行时 panic 或错误。
- 务必检查每一步的 err:Go 不允许忽略返回错误,尤其是 rows.Scan() 和 rows.Err() —— 后者用于捕获循环结束后的潜在错误(如列数不匹配)。
- 资源清理不可省略:使用 defer 关闭 *sql.Rows、*sql.Stmt 和 *sql.DB(后者通常复用,但需确保 Close() 被调用)。
- 避免字符串拼接输出:fmt.Fprintf(w, "...%s...%s", a, b) 比 "..." + a + "..." + b 更安全、高效,且支持类型校验。
? 进阶提示:若列数较多或结构复杂,建议定义结构体并使用 sqlx 库(如 db.Select(&results, query, args...))实现自动映射,大幅提升可维护性。
掌握这一模式后,扩展至三列、四列甚至动态列查询均只需调整变量声明与 Scan 参数列表,逻辑完全一致。










