Mongoose二次请求报错:Connection was force closed问题求助
MongooseError: Connection was force closed 问题排查与解决
问题原因
- 全局连接的竞态冲突:Mongoose默认使用全局单例连接,每次请求调用
mongoose.disconnect()会关闭全局连接,二次请求时可能前一次的断开操作未完成就执行新的connect,导致连接状态混乱,查询时连接已被强制关闭。 - 模型与连接绑定不匹配:导入的
Company、Admin等模型默认绑定Mongoose全局连接,切换数据库时模型未重新绑定到新连接,查询时仍使用已关闭的旧连接。 - 无意义的频繁断连:每次请求都断开全局连接会导致连接频繁创建销毁,既损耗性能,又容易引发连接状态异常。
解决方案
核心思路是使用Mongoose多连接管理,为每个数据库维护独立连接实例,避免操作全局连接,同时让模型动态绑定对应连接。
步骤1:重构模型为动态绑定模式
修改所有模型文件(以Company.js为例),改为导出接收连接实例的工厂函数:
// models/Company.js const mongoose = require('mongoose'); const companySchema = new mongoose.Schema({ email: String, password: String, // 其他字段定义 }); // 传入连接实例创建模型 module.exports = (connection) => { return connection.model('Company', companySchema); };
Admin、Employee等其他模型执行同样修改。
步骤2:修改中间件实现多连接缓存与复用
不再操作全局连接,而是为每个数据库创建/复用独立连接,动态绑定模型:
const jwt = require('jsonwebtoken'); const JWT_SECRET = "PASSjajghjklnaslg"; const mongoose = require("mongoose"); // 导入模型工厂函数 const createCompanyModel = require("../models/Company"); const createAdminModel = require('../models/Admin'); const createEmployeeModel = require('../models/Employee'); const createManagerModel = require('../models/Manager'); const createSubAdminModel = require('../models/SubAdmin'); const URI = "mongodb://0.0.0.0:27017/"; // 缓存已创建的连接,避免重复初始化 const connectionCache = new Map(); const connectToDB = async (req, res, next) => { const token = req.header("auth-token"); if (!token) { next(); return; } try { const data = jwt.verify(token, JWT_SECRET); const dbName = data.companyEmail.replace(/[.]/g, '%2e'); const dbURI = `${URI}${encodeURIComponent(dbName)}`; // 校验数据库是否存在 const adminDb = mongoose.connection.db.admin(); const result = await adminDb.listDatabases(); const dbsNames = result.databases.map(db => db.name); if (!dbsNames.includes(dbName)) { res.status(401).send("Access denied"); return; } // 获取或创建数据库连接 let connection; if (connectionCache.has(dbName)) { connection = connectionCache.get(dbName); // 连接不可用时重新创建 if (connection.readyState !== 1) { connection = await mongoose.createConnection(dbURI, { useNewUrlParser: true, useUnifiedTopology: true }); connectionCache.set(dbName, connection); } } else { connection = await mongoose.createConnection(dbURI, { useNewUrlParser: true, useUnifiedTopology: true }); connectionCache.set(dbName, connection); } // 动态创建模型并执行业务逻辑 if (data.accountType === "company") { const Company = createCompanyModel(connection); const company = await Company.findById(data.company).select("-password"); const anyDoc = await Company.findOne({}); if (!company && !anyDoc) { await connection.db.dropDatabase(); connectionCache.delete(dbName); // 清理无效连接缓存 res.status(401).send("Access denied"); return; } if (!company) { res.status(401).send("Access denied"); return; } res.locals.company = company; next(); return; } else if (["admin", "subAdmin", "manager", "employee"].includes(data.accountType)) { let createModel; switch (req.query.role) { case "admin": createModel = createAdminModel; break; case "subAdmin": createModel = createSubAdminModel; break; case "manager": createModel = createManagerModel; break; case "employee": createModel = createEmployeeModel; break; default: return res.status(400).send("Invalid role"); } const Account = createModel(connection); const account = await Account.findById(data.user).select("-password"); if (!account) { res.status(401).send("Access denied"); return; } res.locals.company = account; next(); return; } else { return res.status(400).send('bad request'); } } catch (err) { console.log(err); res.status(401).send({ errors: ["Access denied"] }); return; } }; module.exports = connectToDB;
额外优化建议
- 连接缓存清理:定期检查缓存中连接的活跃状态,移除长时间未使用的连接,避免资源占用。
- 禁用全局连接操作:确保项目其他代码不再调用
mongoose.connect()或mongoose.disconnect(),所有数据库操作均使用缓存的连接实例。 - 增强错误处理:为连接创建过程添加更细致的错误捕获,比如连接超时、认证失败等场景的针对性处理。
内容的提问来源于stack exchange,提问作者Quiet Coder
相关产品推荐
相关产品推荐

