context in database/sql & gRPC
database/sql and google.golang.org/grpc were designed around context.Context from day one.
Search across all documentation pages
database/sql and google.golang.org/grpc were designed around context.Context from day one.
Passing ctx into query and RPC methods ties database and network work to the same cancellation tree as your HTTP handler.
Use QueryContext, ExecContext, and BeginTx with context for every request-scoped database call.
gRPC client and server methods accept ctx as the first argument; deadlines propagate as RPC timeouts.
Streaming RPCs must check ctx.Done() between Recv and Send calls.
Quick-reference recipe card - copy-paste ready.
func GetOrder(ctx context.Context, db *sql.DB, id string) (string, error) {
var status string
err := db.QueryRowContext(ctx, `SELECT status FROM orders WHERE id = ?`, id).Scan(&status)
return status, err
}When to reach for this:
Query/Exec without Context in any code path reachable from HTTP or gRPC handlers.ctx.Done() fires to avoid goroutine leaks.package main
import (
"context"
"database/sql"
"fmt"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
_ "github.com/mattn/go-sqlite3"
)
func queryStatus(ctx context.Context, db *sql.DB) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
defer cancel()
var s string
err := db.QueryRowContext(ctx, `SELECT 'shipped'`).Scan(&s)
return s, err
}
func dialGRPC(ctx context.Context) (*grpc.ClientConn, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
return grpc.DialContext(ctx, "localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
}
func main() {
db, _ := sql.Open("sqlite3", ":memory:")
defer db.Close()
parent, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
status, err := queryStatus(parent, db)
fmt.Println(status, err)
_, err = dialGRPC(parent)
fmt.Println("grpc dial:", err)
}What this demonstrates:
QueryRowContext honors parent cancel and local child timeout.grpc.DialContext blocks only until the context ends or the connection succeeds.database/sql passes context to driver QueryerContext interfaces; cancellation behavior depends on driver support.ctx ends instead of blocking forever.Tx objects use BeginTx(ctx, opts); commit and rollback are fast but in-flight statements should already use the tx context.codes.DeadlineExceeded when insufficient time remains.| Method | Context variant |
|---|---|
| Query | QueryContext |
| Exec | ExecContext |
| Prepare | PrepareContext |
| Begin | BeginTx(ctx, *TxOptions) |
| Ping | PingContext |
func consume(stream pb.Service_StreamServer) error {
ctx := stream.Context()
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
msg, err := stream.Recv()
if err != nil {
return err
}
_ = msg
}
}Server streams expose Context(); client streams accept the caller's ctx on the initial RPC invocation.
Kubernetes client calls from reconcilers should pass the reconcile context into API requests so watches stop when the manager shuts down.
BeginTx with ctx; keep transactions short.ctx.Done() or handle Recv errors promptly.DialContext with bounded ctx.ctx.Deadline() server-side and clamp work.| Alternative | Use When | Don't Use When |
|---|---|---|
db.SetConnMaxLifetime | Pool hygiene | Per-request cancel (use ctx) |
SQL statement_timeout | Driver-agnostic query cap | Replacing application ctx entirely |
| gRPC keepalive | Detect dead connections | User-facing deadline enforcement |
| ORM session without ctx | Legacy internal batch jobs | Online request handlers |
| Message queue for slow work | Minutes-long processing | Sub-second user-facing reads |
Most modern drivers do; verify for your database in driver docs.
Pair with SQL-level timeouts as a safety net.
Yes for request paths - waiting for a free connection should respect client cancel.
Use metadata.NewOutgoingContext on clients and read from metadata.FromIncomingContext on servers.
Keep metadata small and wire-safe.
Pass ctx to the initial Client call; check ctx.Done() between Send chunks.
Yes - contexts are safe for concurrent reads.
Use separate transactions for writes that must isolate.
errgroup.WithContext cancels siblings when one query fails.
Pass the group ctx into each QueryContext.
Long-running migrations may use context.Background() with ops-controlled cancel.
Online request handlers should not run migrations.
Map context.Canceled to client abort; DeadlineExceeded to timeout responses.
Do not treat them as generic 500 when the client initiated stop.
PrepareContext respects ctx; cached stmts still execute with QueryContext per call.
Unary and stream interceptors can enforce deadlines and attach values before handlers run.
Share logic between HTTP and gRPC middleware where possible.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026