如何安全检查 MySQL 查询返回的结构体数组是否为空

9次阅读

如何安全检查 MySQL 查询返回的结构体数组是否为空

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, 简称=%sn", 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。

text=ZqhQzanResources