You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 18:01:22