本文介绍如何通过 for 循环(尤其是配合 database/sql 的 rows.Next())动态填充 Go 中的结构体切片(如 []Row),涵盖标准扫描模式、字段顺序要求、指针传递要点及常见陷阱。
本文介绍如何通过 for 循环(尤其是配合 database/sql 的 rows.next())动态填充 go 中的结构体切片(如 `[]row`),涵盖标准扫描模式、字段顺序要求、指针传递要点及常见陷阱。
在 Go 中,静态初始化(如 todos := Rows{{Name: "Write presentation"}, {Name: "Host meetup"}})适用于已知数据;但实际开发中,多数场景需从数据库、API 或其他动态源填充结构体切片。此时,for rows.Next() 是最常用且安全的迭代模式。
以下为标准填充流程(以 *sql.Rows 为例):
rows, err := db.Query("SELECT name, completed, due, rcount FROM todos")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var todos Rows // 即 []Row
for rows.Next() {
var r Row
// 注意:Scan 参数必须按 SELECT 字段顺序,且传入字段地址
if err := rows.Scan(&r.Name, &r.Completed, &r.Due, &r.Rcount); err != nil {
log.Printf("scan error: %v", err)
continue // 或 break,视错误策略而定
}
todos = append(todos, r)
}
// 检查 rows 迭代过程中是否发生错误(如类型不匹配)
if err := rows.Err(); err != nil {
log.Fatal("rows iteration error:", err)
}
✅ 关键要点说明:
- rows.Scan() 必须传入字段地址(&r.Name),否则值不会写入结构体;
- SELECT 字段顺序必须与 Scan 参数顺序严格一致,且类型兼容(如 time.Time 对应 DATETIME/TIMESTAMP);
- 每次循环内声明 var r Row,避免复用同一变量导致数据覆盖;
- 务必调用 rows.Close()(推荐用 defer),并检查 rows.Err() —— 它捕获 Scan 后可能发生的底层错误(如读取中断);
- 若数据库字段可能为 NULL,应使用 sql.NullString 等可空类型替代原生类型,否则 Scan 会报错。
⚠️ 常见误区:
- 直接 todos = append(todos, &r) → 存入指针会导致所有元素指向同一内存地址;
- 忘记检查 rows.Err() → 隐藏的扫描后错误易被忽略;
- Scan 参数数量或类型不匹配 → panic 或静默失败。
掌握这一模式,即可稳健地将外部数据批量注入结构体切片,是 Go 数据访问层的基础实践。











