多租户隔离怎么选
多租户的核心矛盾是隔离强度与运维成本:隔离越强,每个租户的连接、备份、升级与监控开销越大。实际选型通常按租户数量与规模差异来定:租户少且合规要求高,可以独立库;租户多且规模相近,用共享结构加 tenantId 更现实,中间档是独立集合或独立索引。三种方案都要在数据访问层强制注入租户条件。
隔离级别对照
| 方案 | 隔离性 | 代价 |
|---|---|---|
| 每租户独立库 | 强,备份与恢复可按租户单独做 | 连接池、监控、升级操作成倍增加 |
| 独立集合 / 独立索引 | 中,可按租户精细管理 | 集合与索引数量随租户增长,元数据压力上升 |
| 共享 + tenantId | 靠过滤条件与权限兜底 | 必须在统一入口强制注入,并防热点租户 |
基本信息
| 主键设计 | 共享结构下索引必须以 tenantId 为前缀,否则查询无法按租户裁剪 |
|---|---|
| 路由 | 搜索引擎可用 routing 把同租户文档固定到同一分片组,减少查询扇出 |
| 权限 | 数据库角色可按库或前缀授权,共享结构的隔离只能靠应用层保证 |
| 规模差异 | 大租户可单独开索引或独立集群,避免拖累其他租户 |
| 合规 | 要求数据驻留或物理隔离的场景直接选独立库 |
典型使用场景
- SaaS 应用起步期:租户多规模小,共享结构加 tenantId 成本最低
- 大客户单独立库:便于按合同约定做备份、恢复与性能保障
- 日志与检索场景:按租户 routing 或独立索引,控制查询扇出
- 租户级限流:热点租户占用配额时需要单独限速与告警
- 租户数据导出与删除:独立结构下按库或索引操作,共享结构下按条件批量清理
共享结构示例(MongoDB)
共享结构示例(MongoDB)
// 索引以 tenantId 为前缀,查询才能按租户裁剪数据
db.orders.find({ tenantId: "t42", status: "open" }).sort({ createdAt: -1 })
db.orders.createIndex({ tenantId: 1, createdAt: -1 })
// 视图兜底:把租户过滤固化成视图,供报表类工具使用
db.createView("orders_t42", "orders", [{ $match: { tenantId: "t42" } }])
// 接入层统一封装:租户条件由框架注入,业务代码不直接拼查询搜索引擎侧用 routing 把同一租户的数据路由到少量分片上,能把一次查询的扇出从「全部分片」压缩到「一块分片」,效果通常比调大分片数更明显。
共享结构下最危险的不是性能而是越权:任何一处漏带 tenantId 的查询都是跨租户数据泄露,必须靠统一数据访问层强制注入并配自动化用例覆盖。