
本文介绍如何在 node.js 中基于 sequelize 实现按登录用户动态切换数据库的多租户方案,通过运行时创建隔离的 sequelize 实例,确保每个用户访问其专属数据库,兼顾数据隔离性与系统可扩展性。
本文介绍如何在 node.js 中基于 sequelize 实现按登录用户动态切换数据库的多租户方案,通过运行时创建隔离的 sequelize 实例,确保每个用户访问其专属数据库,兼顾数据隔离性与系统可扩展性。
在典型的 SaaS 应用中,为不同用户分配独立数据库(Database-per-Tenant)是一种强隔离、高安全性的多租户策略。Node.js + Sequelize 场景下,不能复用全局单例 Sequelize 实例(因其绑定固定数据库配置),而需在每次请求上下文中,根据用户身份动态构建并缓存专属的 Sequelize 实例。
✅ 核心实现思路
- 用户认证后获取租户 DB 信息(如 UserDb1);
- 基于该 DB 名称动态生成 Sequelize 实例(含连接池复用);
- 将实例挂载到请求对象(如 req.db)或通过依赖注入传递;
- (推荐)使用内存缓存(如 Map)避免重复创建相同数据库的实例。
? 示例代码(Express + Sequelize)
// db/tenant-manager.js
const { Sequelize } = require('sequelize');
const tenantCache = new Map();
// 共享基础配置(不含 database)
const baseConfig = {
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
pool: { max: 5, min: 0, idle: 10000 },
logging: false,
};
// 获取租户专属 Sequelize 实例(带缓存)
function getTenantDB(databaseName) {
if (tenantCache.has(databaseName)) {
return tenantCache.get(databaseName);
}
const sequelize = new Sequelize(databaseName, baseConfig.username, baseConfig.password, {
...baseConfig,
database: databaseName, // 关键:动态指定数据库名
});
// 可选:自动同步模型(仅开发期,生产建议用迁移)
// await sequelize.sync();
tenantCache.set(databaseName, sequelize);
return sequelize;
}
module.exports = { getTenantDB };// middleware/auth-and-tenant.js
const { getTenantDB } = require('../db/tenant-manager');
async function authAndBindTenant(req, res, next) {
// 1. 解析 Token 或 Session 获取已登录用户(示例:JWT)
const user = req.user; // 假设 passport/jwt 已注入 req.user
if (!user || !user.database) {
return res.status(401).json({ error: 'Unauthorized: missing tenant database' });
}
try {
// 2. 动态获取该用户的 Sequelize 实例
req.tenantDB = getTenantDB(user.database);
// 3. 可选:验证连接可用性(首次使用时轻量探测)
await req.tenantDB.authenticate();
next();
} catch (err) {
console.error('Failed to connect to tenant DB:', user.database, err);
res.status(500).json({ error: 'Tenant database unavailable' });
}
}
module.exports = authAndBindTenant;// routes/user-data.js
const express = require('express');
const router = express.Router();
router.get('/profile', async (req, res) => {
try {
// ✅ 使用当前租户的数据库实例
const User = req.tenantDB.define('User', {
id: { type: Sequelize.INTEGER, primaryKey: true },
name: Sequelize.STRING,
email: Sequelize.STRING,
});
const profile = await User.findByPk(1); // 查询本库中的用户
res.json(profile);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = router;// app.js(主入口)
const express = require('express');
const authMiddleware = require('./middleware/auth-and-tenant');
const userDataRouter = require('./routes/user-data');
const app = express();
app.use(express.json());
app.use('/api', authMiddleware, userDataRouter); // 统一注入租户 DB⚠️ 注意事项与最佳实践
- 连接池管理:每个 Sequelize 实例维护独立连接池,务必设置合理 max 值(如 5–10),避免海量用户导致数据库连接数爆炸;
- 缓存清理:若支持租户数据库动态增删,需提供 tenantCache.delete(dbName) 接口;
- 模型定义:避免在全局定义模型(如 sequelize.define(...)),应在每次请求中基于 req.tenantDB 重新定义,或使用 Sequelize 的 define() 模式 + sync() 控制;
- 事务限制:跨租户事务不支持(因连接不同),业务逻辑必须严格限定在单租户内;
- 安全性:database 字段必须来自可信源(如用户表内部字段),严禁直接从 URL / body 中拼接数据库名,防止 SQL 注入或越权访问。
✅ 总结
动态数据库路由是多租户架构的关键能力。通过“按需实例化 + 内存缓存 + 请求级绑定”,你可在 Sequelize 中稳健实现用户级数据库隔离。该方案不依赖中间件代理或数据库路由层,完全由应用层控制,灵活且易于监控。后续可结合 Redis 缓存租户元数据、引入连接池健康检查、或集成 TypeORM 的 ConnectionManager 等进阶优化。

















