
本文详解 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: %snThe second is: %sn", 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 参数列表,逻辑完全一致。