
在 go 中使用 `database/sql` 包执行查询时,手动调用 `rows.close()` 容易遗漏,导致连接泄漏;推荐使用 `defer rows.close()` 确保资源及时释放,兼顾安全性与代码可读性。
Go 的 defer 语句是专为这类资源清理场景设计的语言特性——它能保证函数返回前(无论是否发生 panic、是否多路返回)自动执行指定操作。相比传统“先 open、后 close”的显式配对写法,defer 将清理逻辑紧邻资源获取处声明,大幅提升代码健壮性与可维护性。
以下是一个典型优化示例:
rows, err := db.Query("SELECT id, name FROM users WHERE active = $1", true)
if err != nil {
log.Fatal(err)
}
defer rows.Close() // ✅ 清晰、可靠:紧贴 Query 调用,且必定执行
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Printf("scan error: %v", err)
continue // 或 break,不影响 defer 执行
}
// 处理单行数据...
}
// rows.Close() 在此处自动触发(函数返回前)⚠️ 注意事项:
- defer 必须在 rows 变量有效作用域内调用(即不能在 if err != nil 分支外、但 rows 尚未初始化时 defer);
- 若需提前退出循环并确保 Close 已生效(如错误处理后立即返回),defer 依然可靠——它不依赖循环结束,而依赖所在函数的退出;
- 对于 *sql.Tx 或 *sql.Conn 等其他需显式释放的资源,同样适用 defer tx.Rollback() / defer conn.Close() 等模式;
- 不要误用 defer rows.Close() 后再手动调用 rows.Close(),会导致二次关闭(虽通常无害,但属冗余且易引发混淆)。
✅ 总结:将 defer rows.Close() 作为查询后的第一行(紧随 Query/QueryRow 调用之后),是最符合 Go 惯例、最不易出错的资源管理实践。它既消除了遗忘关闭的风险,又让资源生命周期一目了然,是编写生产级数据库操作代码的必备习惯。










