sql.DB 是并发安全的连接池管理器而非数据库连接,内部通过 sync.Pool 和互斥锁实现协程安全;应全局初始化一次并复用,避免重复创建或误调 Close。
sql.db 本身不是“连接”,而是一个带连接池的并发安全句柄,它的协程安全性来自设计层面的抽象和内部同步机制。
sql.DB 的本质不是连接,而是连接池管理器
很多人误以为 sql.DB 就是数据库连接,其实它根本不是。它是一个长期存活的句柄,内部维护着一组可复用的底层 conn 实例,并通过 sync.Pool 和互斥锁协调访问。每次调用 Query、Exec 或 Prepare 时,sql.DB 才会从池中获取一个空闲连接(或新建一个),执行完再归还——整个过程对用户透明。
这意味着:你不需要为每个 goroutine 创建新 sql.DB,也不需要自己加锁控制并发访问。
官方明确保证并发安全,但不等于所有操作都无条件安全
Go 官方文档写得很清楚:“DB is safe for concurrent use by multiple goroutines.” 这个“安全”指:
-
sql.DB的方法(如Query、Exec、Ping)可被任意数量 goroutine 同时调用 - 内部连接分配、回收、超时控制等逻辑已加锁保护
- 不会因并发调用导致内存损坏、panic 或连接状态错乱
但要注意:这个安全不延伸到你手动拿到的 *sql.Conn。比如用 db.Conn(ctx) 获取的连接,必须显式调用 conn.Close(),否则连接永不释放——这不是 sql.DB 的责任,而是你绕过池管理后的自主行为。
常见误用:把 sql.DB 当成“连接”来反复创建或关闭
以下做法会直接破坏协程安全性和性能:
- 在 handler 里每次调用
sql.Open创建新sql.DB→ 连接池失控,文件描述符暴涨 - 把
sql.DB包进结构体后,在每个方法里调用db.Close()→ 后续所有 goroutine 都会遇到sql: database is closed - 用全局变量存
sql.DB却没在程序退出前Close()→ 进程退出时连接未优雅释放,可能卡住
真正该做的只有一件:在 main 初始化一次 sql.DB,设置好 SetMaxOpenConns、SetConnMaxLifetime 等参数,然后把它传给所有需要的地方——传指针,不复制,不重开,不乱关。
最易被忽略的一点:协程安全 ≠ 自动防泄漏。如果某处忘了 rows.Close() 或 stmt.Close(),连接可能被长期占用,最终触发 SetMaxOpenConns 限制造成阻塞。安全是接口层的,资源管理仍是你的责任。











