database/sql mental model
AI-generated
*sql.DB is a concurrency-safe database handle and connection pool, not one
open connection. Create it once, configure pool limits, verify it with Ping,
and share it across handlers.
db, err := sql.Open("driver-name", dsn)
if err != nil {
return err
}
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute)
if err := db.PingContext(ctx); err != nil {
return err
}
Import a driver separately. The standard library defines the common interface; the driver implements the wire protocol.
Querying safely
AI-generated
rows, err := db.QueryContext(ctx,
`SELECT id, name FROM users WHERE active = ?`, true)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Name); err != nil {
return err
}
}
return rows.Err()
Use placeholders and bound arguments rather than formatting user input into SQL.
Placeholder syntax differs by driver. Check both the query error and rows.Err();
iteration can fail after returning some rows.
Transactions
AI-generated
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.ExecContext(ctx, updateSQL, amount, accountID); err != nil {
return err
}
return tx.Commit()
Use only the *sql.Tx methods inside the transaction. Calling db directly may
run on another connection and therefore outside that transaction. A deferred
rollback is harmless after a successful commit.
Further reading: Accessing relational databases in Go.