sql.db 是并发安全的连接池管理器,其方法可被多 goroutine 同时调用而不崩溃,因内部用 sync.mutex/atomic 保护共享资源;但 driver.conn 非并发安全,sql.db 保证每次操作独占连接。

sql.DB 本身不是“连接”,而是一个带锁的连接池管理器,所有并发访问都由它内部同步控制。
为什么 sql.DB 的方法能被多个 goroutine 同时调用而不崩溃
因为 sql.DB 在设计上把所有可能竞争的资源(比如空闲连接列表、正在创建连接的状态、统计计数器)都用 sync.Mutex 或 sync/atomic 包裹了。每次调用 Query、Exec、Ping 等方法时,它会先加锁获取一个可用连接,操作完再归还并解锁——整个过程对上层透明。
你不需要也不应该自己加锁;反过来,如果手动对 sql.DB 实例加锁,反而可能引发死锁或性能瓶颈。
sql.DB 并发安全 ≠ 底层 conn 是线程安全的
真正执行 SQL 的 driver.Conn 实例是**非并发安全**的,一次只能被一个 goroutine 使用。但 sql.DB 保证了这点:它绝不会把同一个 driver.Conn 同时分给两个 goroutine。
- 每个
Query或Exec调用都会独占一个连接,直到该操作完成(包括读完rows或关闭stmt) - 如果你用
db.Conn(ctx)手动获取连接,必须显式调用conn.Close(),否则这个连接会被永久占用 - 预编译语句
db.Prepare()返回的*sql.Stmt也是并发安全的,因为它内部也做了连接复用和同步
SetMaxOpenConns 设置过小会导致 goroutine 阻塞
这是最容易被忽略的并发陷阱:当所有连接都在忙,且 db.SetMaxOpenConns(n) 已达上限时,后续的 Query 调用会**阻塞等待**,直到有连接被释放。这不是 bug,而是设计行为。
常见错误现象包括:
- HTTP handler 响应变慢甚至超时,日志里没报错,但
db.Stats().WaitCount持续上涨 - goroutine 数量暴涨(pprof 查看),大量卡在
database/sql.(*DB).conn的 channel receive 上 - 数据库端看到连接数稳定在
MaxOpenConns,但应用 CPU 不高、QPS 却上不去
建议上线前用 db.Stats() 定期采样,重点关注 WaitCount 和 MaxOpenConnections 是否匹配预期。
别在 defer 里关 db,更别在函数里反复 sql.Open
sql.Open 只是初始化句柄,不建连接;db.Close() 才是真正释放全部连接。所以:
- 全局只做一次
sql.Open,通常在main()开头 -
db.Close()必须放在main()结尾的defer,或由进程生命周期统一管理 - 绝对不要在某个 handler 函数里
sql.Open+defer db.Close()—— 这会快速耗尽文件描述符,且失去连接复用优势
真正容易被忽略的点是:一旦 db.Close() 被调用,所有后续的 Query 都会立即返回 sql: database is closed 错误,且无法恢复。











