
mgo驱动默认连接池过大(4096)导致高并发下连接数失控,即使正确调用`session.close()`仍持续占用tcp连接;根本解法是显式限制`maxpoolsize`并规范会话生命周期管理。
在基于 Go + mgo + MongoDB 构建 REST API 时,一个常见但隐蔽的性能陷阱是连接泄漏(connection leak)——表现为进程持续累积 ESTABLISHED 状态的 TCP 连接,最终耗尽系统文件描述符(如达到 ulimit 1024 上限),导致服务不可用。
问题本质并非 session.Copy() 或 session.Close() 调用遗漏,而是 mgo 的底层连接池机制设计:mgo.Dial() 默认启用连接池,且 maxPoolSize 默认值高达 4096(见 mgo source)。当并发请求频繁 Copy() 会话时,mgo 会按需创建新连接填充池;而 session.Close() 仅将连接归还至池中,而非真正关闭底层 socket——只要池未满、连接空闲,它们就长期保持 ESTABLISHED 状态。
✅ 正确做法:主动约束连接池规模,并确保会话及时释放:
// 初始化全局 master session(单例)
info := &mgo.DialInfo{
Addrs: []string{"localhost:27017"},
Timeout: 10 * time.Second,
PoolLimit: 16, // ? 关键!显式设为合理值(如 8–32,依QPS和DB负载调整)
}
session, err := mgo.DialWithInfo(info)
if err != nil {
log.Fatal("Failed to dial MongoDB:", err)
}
defer session.Close() // master session 通常不 Close,供 Copy 使用
// 每个 HTTP 请求中:
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Copy 新会话(轻量,复用底层连接)
s := session.Copy()
defer s.Close() // ✅ 必须 defer,确保每次请求结束归还连接
// 执行数据库操作
c := s.DB("mydb").C("users")
var user User
err := c.FindId("abc123").One(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ... 响应逻辑
}⚠️ 注意事项:
立即学习“go语言免费学习笔记(深入)”;
- PoolLimit 是每个 DialInfo 实例的连接池上限,不是全局总连接数;若误在每次请求中 Dial(),则完全失效;
- 避免在 handler 内 session.Clone()(已废弃),统一使用 Copy();
- session.SetSafe(&mgo.Safe{}) 等设置无需在每次 Copy 后重复调用,因 Copy 会继承父会话配置;
- 生产环境建议配合连接超时(SocketTimeout)和读写超时(SyncTimeout)进一步加固;
- 升级提示:mgo 已停止维护,推荐迁移到官方驱动 mongo-go-driver,其连接池行为更透明、可监控性更强。
总结:连接泄漏的“真凶”常是默认配置的过度宽松。通过显式设置 PoolLimit 并严格遵循 Copy() → defer Close() 模式,即可在保持高性能的同时彻底规避连接堆积问题。











