独立数据库方案实现租户物理隔离,适合高合规要求场景,但运维成本高;共享集合+tenantid模式省资源却依赖代码严格兜底;混合策略按租户规模动态选型,并需关注连接池、分片键、迁移灰度及tenantid全链路传递。

独立数据库方案:每个租户一个 db,真隔离但运维成本高
租户数据物理隔离,天然避免误查、越权、索引冲突等问题。适合对合规性(如 GDPR)、SLA 或资源配额要求严格的场景。
实操建议:
- 创建租户库时用统一前缀,比如
tenant_abc123,避免命名冲突或特殊字符导致连接失败 - 应用层必须严格校验租户标识(如从 JWT 或请求头提取),再拼接进连接字符串 —— 绝不能 直接拼接用户输入到
db名中,否则有注入风险 - 备份、监控、慢查询分析需按库粒度配置,MongoDB Atlas 上可基于标签分组,但自建集群得靠脚本轮询
admin.command({ listDatabases: 1 })动态发现新库 - 副本集主节点压力会随租户数线性增长:每个库都有自己的 oplog、缓存页、锁队列,1000 个租户可能让
mongod内存占用翻倍
共享集合 + tenantId 字段:省资源但全靠代码兜底
所有租户共用同一套集合(如 orders),每条文档带 tenantId: "abc123"。这是最常见也最容易出事的模式。
关键陷阱和应对:
- 每个查询、更新、聚合都必须显式带上
{ tenantId: "abc123" }条件 —— 漏写一次就可能读到/删掉其他租户数据 -
tenantId字段必须加复合索引,例如db.orders.createIndex({ tenantId: 1, createdAt: -1 });单字段索引效率低,尤其当tenantId基数小(比如只有几十个大客户)时,容易走全表扫描 - 聚合管道里
$lookup关联其他集合时,子查询也得手动加tenantId过滤,MongoDB 不会自动透传 - 唯一约束(如用户邮箱)不能只建在
email上,得改成{ email: 1, tenantId: 1 },否则跨租户重复邮箱会冲突
混合策略:按租户规模动态选型,用 mongos 路由分流
中小租户走共享集合,头部客户单独分库 —— 这需要在应用路由层做判断,而不是靠 MongoDB 自身能力。
落地要点:
- 维护一张
tenant_config集合,记录每个租户的strategy: "shared" | "dedicated"和db_name(后者仅 dedicated 时非空) - 连接池不能复用:shared 租户共用一个
MongoClient实例,dedicated 租户每个需独立实例(或至少独立db对象),否则连接串混用会导致写错库 - 如果用分片集群,
tenantId可作为分片键(shard key),但必须是hashed类型或带高基数前缀,否则热点集中在某个 chunk 上 - 不要试图用 MongoDB 的
role+resource做行级权限控制 —— 它只支持集合/数据库级,且无法动态绑定tenantId
迁移与灰度:从 shared 切到 dedicated 最容易翻车的地方
不是简单导出导入就能完事。真实业务中,租户数据往往分散在十几张关联集合里,且存在跨集合引用(如 order.userId → users._id)。
必须处理的细节:
- 所有 ObjectId 引用字段(如
userId、productId)在新库中仍是合法 ID,但要确保目标库的users集合已存在对应文档 —— 否则关联查询失效 - 触发器、Change Stream 订阅、TTL 索引等依赖库名或集合名的功能,迁移后需重新注册,且旧库的监听不能残留
- 应用发布必须灰度:先切读流量,验证
tenantId过滤逻辑是否被绕过;再切写,同时双写旧库保底,直到新库数据一致 - 别忽略驱动层日志 —— 某些 ORM(如 Mongoose)的
pre('save')钩子可能硬编码了集合名,迁移后会静默写回原库











