database/sql
Go's database/sql package provides a driver-agnostic API for relational databases. The same code works for PostgreSQL, MySQL, SQLite, and any other database that has a Go driver โ you just swap the driver import and the DSN string.
Opening a connection
sql.Open does not connect โ it only validates the arguments and creates the handle. Call db.Ping() to verify the database is reachable:
Querying a single row
QueryRow always returns a *Row. The error (if any) is deferred until Scan.
Querying multiple rows
Always defer rows.Close() โ an unclosed Rows holds a connection from the pool until the function returns.
Executing mutations
Prepared statements
Prepared statements are parsed once on the server, then executed many times with different parameters:
Beyond performance, prepared statements prevent SQL injection: user input is always treated as a parameter value, never as part of the query string.
Transactions
The defer tx.Rollback() pattern is idiomatic: if Commit succeeds, the subsequent Rollback is a no-op. If anything goes wrong before Commit, the deferred Rollback cleans up automatically.
Connection pool settings
sql.DB maintains a pool of connections. Tune it for your workload:
Too few open connections โ throughput bottleneck. Too many โ database server overloaded. Typical starting point: MaxOpenConns = 4 ร CPU cores.
Knowledge Check
Why must you always `defer rows.Close()` after db.Query?
How do prepared statements prevent SQL injection?
What is the purpose of `defer tx.Rollback()` when combined with an explicit `tx.Commit()` call?