单集合+tenant_id字段是最稳妥可扩展方案;必须显式过滤tenant_id以防数据越界,复合索引需以tenant_id为前导字段,分片键建议hashed tenant_id,json schema应强制required。

单集合 + tenant_id 字段是绝大多数场景下最稳妥、可扩展性最强的选择,独立数据库只在极少数强合规或数据结构差异极大的情况下才值得引入。
为什么不能跳过 tenant_id 过滤直接查集合
因为 MongoDB 本身不提供行级租户隔离机制。所有读写操作都必须显式带上 tenant_id 条件,否则极易发生租户间数据越界——比如 A 租户的查询漏写 { tenant_id: "a" },就可能扫出 B 租户的向量文档。
常见错误现象包括:
- 聚合管道中
$lookup指向固定集合名,但没加tenant_id过滤,导致跨租户关联 - 使用
find({})或countDocuments({})统计时忘记限定租户,返回全量结果 - Mongoose 中未在
pre('find')和pre('save')钩子里校验tenant_id是否存在且非空
tenant_id 必须作为复合索引的前导字段
否则向量检索性能会断崖式下跌,尤其在启用 $vectorSearch 时,预过滤失效会导致全集合扫描。
正确做法是:
- 对向量字段(如
embedding)和tenant_id建复合索引:db.collection.createIndex({ tenant_id: 1, embedding: "vector" }) - 分片键建议用
hashed tenant_id,避免大租户数据倾斜到单个分片 - JSON Schema 层强制
tenant_id为 required 字段,防止插入脏数据
独立数据库模式真正适用的两个条件
不是“看起来更安全”,而是满足以下任一硬性约束:
- 租户数量稳定且极少(
- 行业强监管要求物理级隔离(如金融/医疗场景下的 GDPR 磁盘级分离),且 Atlas ACL + 数据库级权限是唯一被审计认可的方式
否则你会立刻遇到问题:
- Atlas 项目默认最多 100 个数据库用户,租户数超限时无法为每个租户配独立账号
- 备份恢复变复杂:
mongodump必须动态生成数据库列表,脚本易错;单个租户误删库,无法用collMod回滚,只能从全量备份捞 - 跨租户统计类需求退回到应用层合并,无法用聚合管道统一处理
向量搜索场景下最容易忽略的隔离点
很多人以为建好 tenant_id 索引就万事大吉,其实 $vectorSearch 的 filter 参数必须显式传入,且不能依赖中间件自动注入——中间件可能被绕过,或在直连驱动、CLI 工具等非标准路径下失效。
示例错误写法:db.collection.aggregate([ { $vectorSearch: { index: "vector_index", path: "embedding", queryVector: [...], limit: 10 } } ])
这会忽略租户上下文,直接扫全量。
正确写法必须带 filter:db.collection.aggregate([ { $vectorSearch: { index: "vector_index", path: "embedding", queryVector: [...], filter: { tenant_id: "a" }, limit: 10 } } ])
这个 filter 是向量检索阶段的硬性过滤,和普通 find() 的 tenant_id 条件一样,缺一不可。











