Go数据库查询需传Context,因QueryContext等方法原生支持超时与取消,避免goroutine挂起;须显式传递带超时的ctx,注意事务、ORM及rows链路中context透传,且context超时应略小于DB层timeout以兜底。

Go数据库查询为什么要传Context
因为 database/sql 的查询方法(如 QueryContext、ExecContext)原生支持 context.Context,不传就用默认的无取消能力的背景上下文,一旦数据库卡住或网络超时,goroutine 就会一直挂着,没法主动中断。
典型表现是:服务压测时 goroutine 数暴涨、HTTP 请求超时了但 DB 查询还在跑、K8s liveness probe 失败却查不到阻塞点。
QueryContext 和 ExecContext 怎么用
替换掉老式 Query/Exec 调用,显式传入带超时或取消信号的 ctx:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE id = ?", userID)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Println("query timed out")
}
return err
}
defer rows.Close()
-
QueryContext、ExecContext、QueryRowContext是标准库提供的唯一支持 context 的入口,别再用Query等无 context 版本 - 超时必须设在业务能接受的范围内,比如 HTTP handler 中建议 ≤ 90% 的路由超时值
- 如果 ctx 已被 cancel(比如父请求结束),查询会立即返回
context.Canceled错误,不是等语句执行完
Context 传递时容易漏掉的三个地方
很多代码只在顶层加了 context,但中间链路断掉了:
立即学习“go语言免费学习笔记(深入)”;
- 调用
rows.Scan()不会响应 cancel —— 它只是内存拷贝,但前提是rows本身得从QueryContext来,否则整个链路无效 - 事务中嵌套查询时,必须把同一个
ctx传给tx.QueryContext,不能用tx.DB.QueryContext,否则会绕过事务连接 - 用
sqlx或gorm等 ORM 时,要确认它们是否透传 context:比如sqlx.GetContext有,但sqlx.Get没有;gorm.Session(&session{Context: ctx})才生效,直接db.First()不行
Context 超时和数据库自身 timeout 的关系
两者不互斥,但优先级不同:context 控制 Go 侧等待时间,DB 层 timeout(如 MySQL 的 wait_timeout)控制连接空闲时长。真正影响查询中断的是 context。
- PostgreSQL 的
statement_timeout是服务端强制中断,但需要在连接串里配(options=-c+statement_timeout=5000),且它不通知 Go 客户端,你仍需靠 context 做超时兜底 - MySQL 的
max_execution_time类似,但仅对 SELECT 生效,UPDATE/DELETE 不触发,所以不能替代ExecContext - 最稳妥的做法:context 设 5s,DB 层 timeout 设 6s,留出网络和驱动开销余量


















