做 SaaS 绕不开的第一个架构决策就是数据隔离。三种主流方案各有适用场景,选错的代价通常在客户数上来之后才显现,那时候改动成本已经很高。
方案一:共享库 + 租户字段
所有租户的数据放在同一套表里,每张表加一个 `tenant_id` 字段,查询时强制带上过滤条件。
优点资源利用率最高,运维最简单,新增租户零成本
缺点越权风险高度依赖代码质量,单租户大数据量会拖累全体
适合租户数量多、单租户数据量小、对隔离要求不极端的产品
这个方案最大的风险是「忘了带 tenant_id」。防线不能只靠开发自觉——必须在 ORM 层或数据访问层做强制注入,让漏写在架构上不可能发生。
方案二:独立 Schema
同一个数据库实例,每个租户一套独立的表结构(PostgreSQL 的 schema、MySQL 的 database)。连接时切换到对应 schema。
优点隔离性明显强于共享表,单租户数据可独立备份恢复
缺点表结构变更要遍历所有租户执行,租户数上千后迁移会很痛苦
适合租户数量中等(几十到几百)、对隔离有要求的 B2B 产品
方案三:独立数据库实例
每个租户一套完全独立的数据库,甚至独立的应用实例。
优点隔离最彻底,可满足最严格的合规要求,性能互不影响
缺点成本最高,运维复杂度随租户数线性增长
适合大客户、金融医疗等强合规行业、私有化交付
实践中通常是混合的
成熟的 SaaS 很少只用一种方案。常见的组合是:
- 默认走共享库:绝大多数中小租户放在共享库,成本最优
- 大客户升级隔离:数据量或合规要求超过阈值的租户,迁移到独立库
- 保留迁移通道:从一开始就设计好租户数据的导出与导入流程,让升级隔离不需要停机重做
第三条最容易被忽略,也最重要。如果早期没考虑迁移,等第一个大客户提出隔离要求时,往往只能手工搬数据。
除了存储,还有三个地方要隔离
- 缓存:Redis 的 key 必须带租户前缀,否则串数据比数据库更隐蔽
- 文件存储:对象存储的路径按租户分区,签名 URL 要校验归属
- 任务队列:一个租户的大批量任务不能把队列占满,需要按租户配额或独立队列
实际事故里,缓存串数据的比例远高于数据库越权——因为数据库有 ORM 层保护,而缓存的 key 经常是手写拼接的。
一个检查清单
- 数据访问层是否强制注入租户条件,漏写能否在编译或测试阶段被发现
- 缓存 key、文件路径、队列名是否都带租户标识
- 是否有跨租户的定时任务,它的租户上下文从哪来
- 单个租户能否通过大批量操作影响其他租户的可用性
- 租户数据能否一键完整导出(这既是客户诉求,也是隔离升级的前提)