
在 go 中使用 database/sql 查询单行数据时,若误用 query 而非 queryrow,可能导致对空结果集调用 len() 或遍历时 panic;正确做法是优先使用 queryrow 处理单行预期场景,或通过 rows.next() 循环配合标志位判断结果是否存在。
当你执行类似 SELECT id, secret, shortname FROM beehives WHERE shortname = ? 这类预期最多返回一行的查询时,应避免使用 db.Query() 返回 *sql.Rows 后直接对底层切片做 len(rows) 检查——因为 *sql.Rows 并非 Go 原生切片类型(它是一个封装结构体),其本身不支持 len() 操作,强行调用会触发 runtime panic。同理,rows == nil 也无效,因为 db.Query() 即使无结果也会返回一个非 nil 的 *sql.Rows 实例。
✅ 推荐方案:使用 db.QueryRow()(适用于单行场景)
QueryRow 是专为“至多一行”设计的 API,它始终返回非 nil 的 *sql.Row,并将错误(如无匹配行)延迟到 .Scan() 时抛出:
var id int
var secret, shortname string
err := db.QueryRow(
"SELECT id, secret, shortname FROM beehives WHERE shortname = ?",
beehive,
).Scan(&id, &secret, &shortname)
switch {
case err == sql.ErrNoRows:
log.Printf("蜂箱 %s 未找到", beehive)
case err != nil:
log.Fatal("查询失败:", err)
default:
fmt.Printf("查得蜂箱: ID=%d, 密钥=%s, 简称=%s\n", id, secret, shortname)
}⚠️ 注意:务必使用 ? 占位符进行参数化查询,而非字符串拼接(防止 SQL 注入),原示例中 '%s' 的写法存在严重安全风险。
❌ 若必须使用 db.Query()(例如需兼容多行但当前逻辑只取首行),则需通过迭代判断:
rows, err := db.Query(
"SELECT id, secret, shortname FROM beehives WHERE shortname = ?",
beehive,
)
if err != nil {
log.Fatal("查询执行失败:", err)
}
defer rows.Close()
found := false
for rows.Next() {
var id int
var secret, shortname string
if err := rows.Scan(&id, &secret, &shortname); err != nil {
log.Fatal("扫描行失败:", err)
}
// 处理第一行后即可 break(若仅需首行)
fmt.Printf("匹配到蜂箱: %s (ID=%d)\n", shortname, id)
found = true
break // 可选:仅处理首行
}
if !found {
log.Printf("蜂箱 %s 不存在", beehive)
}
// 必须检查 rows.Err() —— 迭代结束后确认无底层错误
if err := rows.Err(); err != nil {
log.Fatal("遍历结果集时出错:", err)
}? 关键要点总结:
- len() 和 == nil 对 *sql.Rows 无效,因其不是切片;
- 单行查询首选 QueryRow + Scan,语义清晰且自动处理 ErrNoRows;
- 多行场景下,用 rows.Next() 循环驱动,并以布尔标志(如 found)记录是否进入循环体;
- 每次使用 *sql.Rows 后必须调用 rows.Close()(建议 defer),并检查 rows.Err() 确保无迭代异常;
- 坚决使用参数化查询(? 占位符),杜绝字符串拼接 SQL。










