A common mistake is treating rows.Close() as harmless cleanup. sql.Rows represents query results and implements a closer; omitting closure can leak a database connection and prevent its return to the pool. A plain defer rows.Close() also discards any closure error. section 10.5.2 section 7.7
Closing query results
After a successful query, defer closure before using the rows: rows, err := db.Query(query, clientID) if err != nil { return 0, err } defer func() { if err := rows.Close(); err != nil { log.Printf("failed to close rows: %v", err) } }() // Use rows This logs a failure to release the connection instead of silently ignoring it. If closure errors must reach the caller, use named result parameters and assign the deferred error carefully, while preserving an earlier query or scan error. section 10.5.2 section 7.7
Choose the cleanup policy according to the importance of the closure error:
- Use defer rows.Close() only when intentionally accepting the ignored error. section 7.7
- Use a deferred function with logging when failed release should be observable. section 7.7 section 10.5.2
- Use named result parameters when the deferred closure error must be propagated; avoid replacing an existing operation error. section 7.7
// Open connection
rows, err := db.Query("SELECT * FROM CUSTOMERS") #1
if err != nil {
return err
}
defer func() { #2
if err := rows.Close(); err != nil {
log.Printf("failed to close rows: %v\n", err)
}
}()
// Use rows
Shows the normal placement of deferred rows.Close immediately after a successful Query call. section 10.5.2
