SQL with database/sql
Use `database/sql` handles deliberately: context-aware queries, bounded lifetimes, and transaction scope that matches the unit of work.
Canonical guidance
- use
QueryContext,ExecContext, andBeginTx - close rows promptly and check
rows.Err() - keep transactions as small as the unit of work allows
Use when
- standard SQL backends
- connection pooling via
sql.DB - request-scoped queries and transactions
Avoid
- context-free database calls in request paths
- forgetting to close
Rows - giant transactions around unrelated work
Preferred pattern
rows, err := db.QueryContext(ctx, q, accountID)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
// scan rows
}
return rows.Err()
Anti-pattern
- opening transactions far above the boundary that knows the actual unit of work
Explanation: This anti-pattern is tempting because it centralizes setup, but it holds locks and resources longer than necessary.
Why
database/sqlworks well when lifetime, context, and transaction scope stay explicit
Related pages
Sources
- database/sql package - Go Team
- Go Concurrency Patterns: Context - Sameer Ajmani