go-database
Best practices for Go database access. Covers parameterized queries, struct scanning, transactions, error handling, and connection pooling.
SKILL.mdskills/go/go-database/SKILL.md
GitHubBest Practices
- Use sqlx or pgx — Avoid ORMs like GORM for complex production systems. They hide SQL and cause performance issues.
- Parameterized queries — NEVER concatenate strings into SQL. Use
?or$1placeholders. - Propagate Context — Always use
*Contextmethods (QueryContext,ExecContext) to respect timeouts and cancellation. - Handle
sql.ErrNoRows— Distinguish between "no data found" and an actual database error. - Always close rows — Use
defer rows.Close()immediately after checking the error fromQueryContext. - Transactions for atomicity — Wrap multi-statement updates in a transaction.
- Handle NULLs — Use pointers (e.g.,
*string) orsql.NullStringfor columns that can be null. - Connection pooling — Configure
SetMaxOpenConnsandSetMaxIdleConnsbased on your load.
Query Examples
Selecting a single row (sqlx)
var user User
err := db.GetContext(ctx, &user, "SELECT * FROM users WHERE id = $1", id)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
Selecting multiple rows (sqlx)
var users []User
err := db.SelectContext(ctx, &users, "SELECT * FROM users WHERE status = $1", "active")
Executing an update
result, err := db.ExecContext(ctx, "UPDATE users SET status = $1 WHERE id = $2", "inactive", id)
if err != nil {
return err
}
rowsAffected, _ := result.RowsAffected()
Transactions
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback() // Safe to call even if committed
if _, err := tx.ExecContext(ctx, ...); err != nil {
return err
}
return tx.Commit()
Connection Pool Configuration
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
Anti-patterns
- ❌ SQL Injection:
fmt.Sprintf("SELECT * FROM users WHERE name='%s'", name). - ❌ Leaking connections: Forgetting
rows.Close(). - ❌ Ignoring
rows.Err(): Always checkrows.Err()after thefor rows.Next()loop. - ❌ ORMs for everything: Resulting in N+1 query problems.
- ❌ Long-running transactions: Holding locks too long and blocking other operations.