
mgo 默认连接池过大(4096)导致并发请求下连接数持续增长、无法及时释放,最终触发系统文件描述符上限;正确做法是显式限制 `maxpoolsize` 并规范 session 生命周期管理。
在 Go 应用中使用 mgo 连接 MongoDB 时,一个常见但隐蔽的问题是:看似正确调用 session.Close(),连接却未及时回收,最终耗尽系统文件描述符(如 ulimit -n 限制的 1024)。你观察到 lsof 显示大量 ESTABLISHED 状态的 TCP 连接指向 localhost:27017,正是这一问题的典型表现。
根本原因在于:mgo.Dial() 默认创建的连接池(MaxPoolSize)为 4096,远超实际并发需求。即使每个请求后调用 session.Copy().Close(),mgo 仍会为后续请求复用并长期保活空闲连接——尤其在低频高并发或长连接场景下,连接不会主动断开,而是滞留在池中等待复用,最终累积至系统级资源瓶颈。
✅ 正确解决方案是 显式配置连接池大小 + 严格遵循 session 使用范式:
// 初始化全局 master session(仅一次)
info := &mgo.DialInfo{
Addrs: []string{"127.0.0.1:27017"},
Timeout: 10 * time.Second,
Database: "mydb",
// 关键:限制最大连接数(根据实际 QPS 和部署规模调整,通常 10–100 足够)
PoolLimit: 32, // ⚠️ 注意:mgo 中对应字段名为 PoolLimit(非 MaxPoolSize)
}
session, err := mgo.DialWithInfo(info)
if err != nil {
log.Fatal("Failed to dial MongoDB:", err)
}
defer session.Close() // 全局 session 在程序退出时关闭
// 每个 HTTP 请求中:
func handler(w http.ResponseWriter, r *http.Request) {
// 1. 复制 session(轻量操作,不新建连接)
s := session.Copy()
defer s.Close() // ✅ 必须确保每次 Copy 后都 Close
// 2. 执行数据库操作
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
}
// ... 返回响应
}? 关键注意事项:
- PoolLimit 是 mgo 中控制连接池大小的字段(不是 MaxPoolSize),默认值为 4096,建议根据服务吞吐量设为 20–50(开发环境可设为 10,生产环境按压测结果调整);
- session.Copy() 是线程安全的,但必须配对调用 s.Close() —— 推荐用 defer 保证执行,避免因 panic 或提前 return 导致遗漏;
- 不要为每个请求 Dial() 新 session,这会彻底绕过连接池,造成更严重的资源泄漏;
- 若已升级到 mongo-go-driver(官方驱动),请迁移到 *mongo.Client 的 Connect/Disconnect 模式,其连接池行为更可控且文档完善。
? 总结:连接泄漏往往不是“没关”,而是“关得太晚”或“池子太大”。通过合理设置 PoolLimit + 严格 Copy/Close 配对,即可稳定支撑高并发 MongoDB 访问,无需修改系统 ulimit 或 MongoDB 配置——后者只是掩盖问题,而非根治。











