goroutine 中直接复用全局 *sql.DB 安全,但需正确配置连接池参数、所有调用带超时 context、显式关闭 rows、事务不可跨 goroutine 使用。
sql.DB 是安全的,但必须避免手动管理连接Go 的 database/sql 包本身已内置连接池,sql.DB 实例是并发安全的,可被任意数量的 goroutine 同时调用 Query、Exec 等方法。不需要、也不应该为每个 goroutine 新建一个 sql.DB 或尝试“复用连接对象”。常见错误是误以为要自己维护连接生命周期,结果导致连接泄漏或 panic。
正确做法是:全局初始化一个 *sql.DB,设置好连接池参数,之后所有 goroutine 直接使用它。
db.SetMaxOpenConns(n) 控制最大打开连接数(含正在使用 + 空闲),设太小会排队阻塞,设太大可能压垮数据库db.SetMaxIdleConns(n) 控制空闲连接上限,建议 ≤ MaxOpenConns,否则空闲连接不会被回收db.SetConnMaxLifetime(d) 强制连接在存活时间后关闭(推荐 5–30 分钟),防止因网络中断或数据库侧超时导致 stale connectiondb.SetConnMaxIdleTime(d)(Go 1.15+)控制空闲连接最长保留时间,避免长期空闲后被中间件或防火墙断连sql.Tx 不是并发安全的一旦调用 db.Begin() 得到 *sql.Tx,该事务对象只能由创建它的 goroutine 使用。把它传给其他 goroutine 并发执行 tx.Query 或 tx.Exec 会导致 panic 或数据不一致 —— 因为 sql.Tx 内部持有单个底层连接,且未加锁保护。
常见误用场景:启动多个 goroutine 并行写入,每个都试图复用同一个 tx。
db.Exec 等非事务接口,让连接池自动分配连接context deadline exceeded

当某个 goroutine 执行耗时 SQL(如未加 limit 的全表扫描、大字段读取、无索引 join)且未设 context 超时,它会独占一个连接较久,导致后续请求在 db.GetConn 阶段等待,最终触发 context.DeadlineExceeded 错误。这不是数据库挂了,而是连接池被拖满。
解决核心是:所有数据库调用必须带带超时的 context.Context。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE status = ?", status)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Println("query timed out")
}
return err
}
db.Query / db.Exec 等无 context 版本,它们默认无超时r.Context(),而非 context.Background()
context.WithCancel 在业务逻辑中主动中断(如用户取消上传)rows.Close() 或未处理 defer 作用域
调用 db.Query 或 db.QueryRow 返回的 *sql.Rows 或 *sql.Row 必须显式关闭,否则底层连接不会归还给连接池。常见疏漏点:
rows.Next() 出错后直接 return,忘了 rows.Close()
defer rows.Close() 但 defer 写在错误检查之前,导致 rows == nil 时 panicrows 传给另一个函数处理,但调用方没关,接收方也没关推荐写法:
rows, err := db.QueryContext(ctx, "SELECT id, name FROM posts")
if err != nil {
return err
}
defer rows.Close() // 安全:rows 非 nil
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
return err
}
// 处理数据
}
if err := rows.Err(); err != nil {
return err
}
真正难排查的是那些隐式持有连接的场景:比如 ORM 封装、自定义 scanner、或第三方库内部未关闭 Rows。遇到连接数缓慢上涨,优先检查所有 Query 调用点是否配对了 Close。