之前给大家分享了尝试给多租户系统做数据隔离时碰到的一些问题,今天给大家分享下多租户系统数据隔离的常用方案,以及每种方案的优劣势。
为什么要考虑数据隔离?
在选择具体方案之前,先想想为什么要做数据隔离。
1. 安全是头等大事
想象一下,你的客户 A 是可口可乐,客户 B 是百事可乐。如果因为代码里少写了一个 WHERE tenant_id = ?,导致可口可乐能看到百事的销售数据…这不仅仅是 Bug,这是事故。
更严重的是,如果你服务的是医疗、金融行业,数据泄露可能直接违反 GDPR、HIPAA 等法规,罚款能让公司关门。
2. 性能也是个问题
共享资源就意味着相互影响。某个大客户跑了个全表扫描,其他客户的查询都变慢了。这种"一个人生病,全家吃药"的情况,对 SaaS 业务来说是致命的。
3. 客户信任和商务需求
有些大客户在签合同时会明确要求:“我的数据必须独立存储”。这不是技术问题,是商务问题。你不答应,合同就签不下来。
三种常见的隔离方案
好,现在我们知道了"为什么要做",接下来看看"怎么做"。业界主流有三种方案,各有利弊。
方案一:物理隔离 - 每个租户独立数据库
这是最彻底的方案:每个租户一个独立的数据库实例。
租户 A -> 数据库 A (db_tenant_a)
租户 B -> 数据库 B (db_tenant_b)
租户 C -> 数据库 C (db_tenant_c)
优点:
- ✅ 安全性最高:物理隔离,根本不可能串数据
- ✅ 性能完全独立:谁也不影响谁
- ✅ 合规性好:轻松满足各种审计要求
- ✅ 支持深度定制:某个租户要加字段?随便改
缺点:
- ❌ 成本高:100 个租户就是 100 个数据库,资源消耗巨大
- ❌ 运维复杂:升级数据库版本?要操作 100 次
- ❌ 扩展性差:租户数量增长时,基础设施压力巨大
- ❌ 跨租户分析困难:想统计所有租户的订单总量?很麻烦
适合场景:
- 租户数量少(< 100)
- 客户对安全性要求极高(金融、医疗)
- 客户愿意为独立环境付费
- 每个租户需要深度定制
方案二:PostgreSQL Schema 隔离 - 共享数据库,独立 Schema
这是个折中方案:所有租户共用一个数据库实例,但每个租户有独立的 Schema(或者叫命名空间)。
数据库实例 (PostgreSQL)
├── schema_tenant_a
│ ├── orders
│ └── customers
├── schema_tenant_b
│ ├── orders
│ └── customers
└── schema_tenant_c
├── orders
└── customers
优点:
- ✅ 安全性较好:有一定的逻辑隔离
- ✅ 成本适中:节省了数据库实例成本
- ✅ 备份恢复灵活:可以单独备份某个租户的 Schema
- ✅ 支持一定的定制:不同 Schema 可以有不同的表结构
缺点:
- ❌ Schema 数数量有限制:PostgreSQL Schema 理论上无限,但过多会有性能问题
- ❌ 连接池管理复杂:需要动态切换 Schema
- ❌ 跨租户查询麻烦:需要跨 Schema join
- ❌ 性能仍然相互影响:共享同一个数据库实例的 CPU、内存、IO
实现要点:
主要是在请求进来时动态切换 Schema,比如通过拦截器识别租户后执行 SET search_path TO schema_xxx,让后续查询自动路由到对应的 Schema。
适合场景:
- 租户数量中等(100-1000)
- 数据安全要求较高,但预算有限
- 部分租户需要定制化
- 使用 PostgreSQL 等支持 Schema 的数据库
方案三:共享表 + tenant_id - 逻辑隔离
这是最经济的方案:所有租户共用同一套表,通过 tenant_id 字段来区分数据。
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
tenant_id VARCHAR(50) NOT NULL, -- 关键字段
order_no VARCHAR(100),
amount DECIMAL(10,2),
created_at TIMESTAMP,
INDEX idx_tenant_id (tenant_id)
);
查询时需要始终带上租户条件:
SELECT * FROM orders WHERE tenant_id = 'tenant_123' AND status = 'paid';
优点:
- ✅ 成本最低:一套表服务所有租户
- ✅ 运维简单:升级一次,所有租户生效
- ✅ 扩展性好:轻松支持上万租户
- ✅ 跨租户分析方便:一个 SQL 搞定统计需求
缺点:
- ❌ 安全风险最高:代码里漏了
WHERE tenant_id = ?就完蛋 - ❌ 性能相互影响:大租户的慢查询会拖累小租户
- ❌ 无法定制:所有租户必须用相同的表结构
- ❌ 数据恢复粒度粗:误删某个租户的数据,恢复很麻烦
适合场景:
- 租户数量大(> 1000)
- 小微客户为主,付费能力有限
- 数据敏感度较低(非金融、医疗)
- 需要频繁的跨租户数据分析
进阶方案:混合隔离
实际业务中,我们往往不会只用一种方案,而是根据租户级别采用不同的隔离策略。
API Gateway
(识别租户 & 路由)
|
+----------------+----------------+
| | |
大客户 中等客户 小客户
(独立数据库) (独立 Schema) (共享表)
tenant_vip tenant_001-100 tenant_101-9999
| | |
[MySQL] [PostgreSQL] [PostgreSQL]
multi-schema shared table
分级策略:
| 租户级别 | 隔离方案 | 原因 |
|---|---|---|
| 企业版 | 独立数据库 | 商务要求 + 定制化 |
| 专业版 | 独立 Schema | 性能保障 |
| 基础版 | 共享表 | 成本优先 |
这种方式既满足了大客户的个性化需求,又控制了整体成本。关键是做好租户升级时的数据迁移,需要保证迁移过程中数据不丢失、服务不中断。