如何使用Mongoose切换MongoDB数据库?多用户分库场景疑问
子域名对应独立MongoDB数据库切换问题
需求说明
- 为不同用户分配独立MongoDB数据库,例如子域名
www.user1.myfrontend.com、www.user2.myfrontend.com、www.user3.myfrontend.com分别对应databaseOfUser1、databaseOfUser2、databaseOfUser3 - 用户发起GET请求时,后端需连接对应数据库查询产品列表
已实现步骤与代码
1. 应用启动时连接config数据库
db.js
const connect = (uri, app, database="config") => { const db= mongoose .createConnection(uri,{ useNewUrlParser: true, useUnifiedTopology: true }) db.on('open', () => { mongoose.connection.useDb("config") app.emit('dbReady'); return true; }); db.on('error', (err) => { console.log(`Couldn't connect to database': ${err.message}`); return false; }); };
server.js
db.connect(process.env.MONGODB_URL, app); app.on('dbReady', function () { server.listen(PORT, () => { console.info(`> Frontend is hosted @: ${process.env.BASE_URL}`); console.info(`> Database is hosted @: ${process.env.mongodb_url}`); console.info(`> Listening on port ${PORT}`); }); });
2. 请求时查询子域名对应数据库
app.js
const AccessConfig = require('./models/schemas/AccessConfigSchema'); const db = require('./models/db'); app.use(async (req, res, next) => { const subdomain = req.subdomains[req.subdomains.length - 1]; try { let database = await AccessConfig.findOne({ subdomain: subdomain}); if (!database) database= await AccessConfig.findOne({ subdomain: "demo"}); console.log(database); db.useDb(database); next(); } catch (e) { console.log(e.message) return res.status(500).send('Error: ' + e.message); } });
遇到的问题
- 当前数据库并未切换,不确定实现是否正确
- 担心频繁切换会导致连接过多的问题
问题分析与解决方案
1. 数据库未切换的核心原因
你的代码存在两个关键错误:
- 混淆了独立连接实例与默认连接:
mongoose.createConnection返回的是独立连接对象,但你后续调用的mongoose.connection.useDb操作的是Mongoose默认连接,两者并非同一实例,导致config数据库的连接逻辑无效。 db.useDb(database)逻辑错误:首先db.js未暴露useDb方法;其次从AccessConfig查询到的是完整文档对象,而非数据库名称,无法直接用于切换;最后未将切换后的连接绑定到当前请求上下文,后续路由无法感知。
2. 正确实现方案
第一步:重构db.js,维护连接池
通过缓存机制避免重复创建连接,同时区分config数据库与用户数据库的连接:
const mongoose = require('mongoose'); // 缓存已创建的数据库连接,key为数据库名称 const connections = {}; // 全局config数据库连接实例 let configConnection; // 初始化config数据库连接 const connectConfig = (uri, app) => { configConnection = mongoose.createConnection(uri, { useNewUrlParser: true, useUnifiedTopology: true }); configConnection.on('open', () => { console.log('Connected to config database'); app.emit('dbReady'); }); configConnection.on('error', (err) => { console.log(`Couldn't connect to config database: ${err.message}`); }); }; // 获取或创建指定数据库的连接 const getConnection = (dbName, uri) => { if (connections[dbName]) { // 连接已存在,直接返回缓存实例 return connections[dbName]; } // 创建新连接并缓存 const conn = mongoose.createConnection(uri, { useNewUrlParser: true, useUnifiedTopology: true, dbName: dbName, poolSize: 10 // 控制单个数据库连接池的最大连接数 }); connections[dbName] = conn; return conn; }; module.exports = { connectConfig, getConnection, getConfigConnection: () => configConnection };
第二步:修改server.js,初始化config连接
const db = require('./models/db'); db.connectConfig(process.env.MONGODB_URL, app); app.on('dbReady', function () { server.listen(PORT, () => { console.info(`> Frontend is hosted @: ${process.env.BASE_URL}`); console.info(`> Database is hosted @: ${process.env.MONGODB_URL}`); console.info(`> Listening on port ${PORT}`); }); });
第三步:绑定AccessConfig模型到config连接
在AccessConfigSchema.js中,确保模型使用config数据库连接:
const mongoose = require('mongoose'); const db = require('../db'); const AccessConfigSchema = new mongoose.Schema({ subdomain: String, dbName: String // 存储对应用户的数据库名称 }); // 使用config连接创建模型 module.exports = db.getConfigConnection().model('AccessConfig', AccessConfigSchema);
第四步:修改app.js中间件,正确绑定请求连接
const AccessConfig = require('./models/schemas/AccessConfigSchema'); const db = require('./models/db'); app.use(async (req, res, next) => { const subdomain = req.subdomains[req.subdomains.length - 1]; try { let configDoc = await AccessConfig.findOne({ subdomain: subdomain}); if (!configDoc) { configDoc = await AccessConfig.findOne({ subdomain: "demo"}); } const targetDbName = configDoc.dbName; // 获取目标数据库的连接实例 const targetConn = db.getConnection(targetDbName, process.env.MONGODB_URL); // 将连接挂载到请求对象,供后续路由使用 req.dbConn = targetConn; next(); } catch (e) { console.log(e.message) return res.status(500).send('Error: ' + e.message); } });
第五步:在路由中使用请求连接操作数据
以产品列表接口为例:
const ProductSchema = require('./models/schemas/ProductSchema'); app.get('/products', async (req, res) => { // 使用当前请求的连接创建模型 const Product = req.dbConn.model('Product', ProductSchema); const products = await Product.find(); res.json(products); });
3. 连接过多问题的解决
- 上述实现通过
connections对象缓存连接,每个数据库仅创建一次连接,避免重复创建。 - 通过
poolSize参数控制单个数据库连接池的最大连接数,防止连接数过载。 - MongoDB连接池会自动管理闲置连接,无需手动清理,若有特殊需求可添加定时器定期回收长时间未使用的连接。
内容的提问来源于stack exchange,提问作者MSOS
相关产品推荐
相关产品推荐

