mongoclient实例默认启用连接池,应全局单例初始化;每次请求新建实例会导致连接数爆炸,必须在应用启动时创建并复用,且避免调用close()。

MongoClient 实例本身就是连接池,不需要额外“开启”或手动管理连接复用——只要不重复 new、不乱 close,池就自动生效。
为什么每次请求都 new MongoClient 会炸掉连接数
这是最常见的误用:把 MongoClient 初始化写在路由处理函数里,比如 app.post 内部。后果是每秒 10 个请求,就可能新建 10 个独立连接池,每个池默认最多 100 连接,叠加监控连接后,很快打满服务器或 Atlas 免费层的连接上限。
- ✅ 正确做法:在应用启动时(模块顶层)创建并
connect()一次,导出复用 - ❌ 错误写法:
const client = new MongoClient(uri); await client.connect(); ... await client.close();出现在每次 HTTP 请求中 - ⚠️ 注意:调用
client.close()后,该实例彻底失效,再用会抛Topology was destroyed
maxPoolSize 不是“越大越好”,而是要匹配实际并发压力
默认 maxPoolSize: 100 对中小流量服务够用;但如果你的应用有大量并行 DB 操作(比如批量导入、实时聚合),连接可能排队等待,触发 waitQueueTimeoutMS 超时错误。
- 调高前先观察真实负载:用
db.currentOp({ "secs_running": { "$gt": 1 } })看慢操作,或 Atlas 控制台看 “Active Connections” 曲线 - 设为 200 是常见折中值,写法:
new MongoClient(uri, { maxPoolSize: 200 }) - 别忽略
minPoolSize:设为 5 可避免冷启动时反复建连,但对低频服务意义不大 -
maxIdleTimeMS建议设为 60000(1 分钟),防止 NAT/防火墙主动断连后还留着僵尸连接
insertOne 和 insertMany 的失败行为完全不同
它们底层都走连接池,但错误传播逻辑差异极大,直接影响你是否需要重试或兜底。
-
insertOne:单文档,失败即抛错(如重复键、字段类型不符),不会影响其他操作 -
insertMany默认ordered: true:遇到第一条失败就中断,后续文档全不插;加{ ordered: false }才跳过错误项继续 - ⚠️ 注意:
ordered: false不捕获 schema 校验失败(比如 required 字段缺失),这类错误仍会中断整个操作 - 返回值差异:
insertOne返回{ insertedId };insertMany返回{ insertedCount, insertedIds },冲突项不计入insertedCount
本地开发 vs Atlas 免费层,retryWrites 参数不能一概而论
连接字符串末尾的 ?retryWrites=false 不是通用优化,而是绕过某些部署的写关注限制。
- 本地单节点 MongoDB 或 ReplicaSet:可以删掉这个参数,让驱动自动重试临时网络抖动
- Atlas 免费层(M0)或某些托管服务:必须加
retryWrites=false,否则可能报WriteConcernError - 如果用
readPreference=secondary,还要注意retryWrites在从节点上无效,得配合maxStalenessSeconds控制延迟容忍度
真正容易被忽略的点是:连接池不是“开了就稳”,它依赖你全程只用一个 client 实例 + 不提前 close() + 错误时正确监听 error 事件。漏掉任意一环,连接数就可能悄无声息地爬升到报警阈值。











