多租户 SaaS 数据库架构:共享库、独立库与数据隔离
多租户数据库设计的核心是在成本、隔离性、可运维性和大租户扩展之间做权衡。
多租户 SaaS 数据库架构:共享库、独立库与数据隔离
SaaS 系统通常服务多个客户。每个客户都是一个租户,租户之间要共享同一套产品能力,但数据必须隔离。
多租户数据库设计的难点在于:小租户很多时要控制成本,大租户变大后要能迁移,安全合规场景又要求更强隔离。
方案一:共享库共享表
所有租户的数据放在同一套表里,通过 tenant_id 区分。
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
order_no VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(12, 2) NOT NULL,
created_at TIMESTAMP NOT NULL,
UNIQUE (tenant_id, order_no)
);
CREATE INDEX idx_orders_tenant_created
ON orders (tenant_id, created_at);
这种方式成本最低,开发和运维最简单。缺点是隔离性弱,所有查询都必须带 tenant_id,否则可能出现越权访问。
共享表适合早期 SaaS、小租户较多、数据规模可控的场景。
方案二:共享库独立 Schema
每个租户一个 schema,比如 tenant_1001.orders、tenant_1002.orders。
这种方式比共享表隔离性更好,表结构也可以在一定程度上独立演进。但 schema 数量太多后,迁移、DDL、备份和监控会变复杂。
它适合租户数量中等、对隔离性有要求但又不想每个租户独立部署数据库的场景。
方案三:每租户独立库
每个租户独立数据库,甚至独立实例。
这种方式隔离性最强,方便单租户备份、恢复、迁移和定制化,也更容易满足部分合规要求。缺点是成本高,运维复杂,租户数量多时管理压力很大。
它适合大客户、私有化部署、强合规、数据量差异很大的 SaaS。
tenant_id 是第一等字段
在共享表模式下,tenant_id 不是普通字段,而是所有数据访问的安全边界。
所有唯一键、索引、查询条件、缓存 Key、消息事件、对象存储路径,都应该包含租户维度。
cache key: tenant:{tenant_id}:user:{user_id}
kafka key: tenant_id + business_id
object path: /tenant/{tenant_id}/exports/2026-05/report.csv
如果只在 API 层过滤租户,而底层查询没有强约束,迟早会出现越权风险。
大租户迁移
共享表早期很舒服,但大租户会带来热点和容量问题。一个租户的数据量可能超过其他所有租户之和。
因此系统要预留大租户迁移能力:
- 先识别大租户。
- 对大租户做数据全量迁移。
- 用 CDC 追增量。
- 双写或双读校验。
- 切换路由。
- 保留回滚窗口。
租户路由表非常关键:
CREATE TABLE tenant_routes (
tenant_id BIGINT PRIMARY KEY,
database_type VARCHAR(32) NOT NULL,
database_key VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL
);
业务服务通过路由表决定某个租户应该访问共享库还是独立库。
权限与审计
多租户系统必须有审计日志。谁访问了哪个租户的数据,导出了哪些字段,什么时候操作,都应该能追踪。
后台管理系统尤其要谨慎。运营或客服人员跨租户查询时,必须有明确授权和审计。
小结
多租户数据库没有唯一答案:
- 共享表成本低,适合早期和小租户。
- 独立 Schema 隔离更强,但运维更复杂。
- 独立库适合大客户和强合规。
成熟 SaaS 往往是混合架构:大多数租户在共享库里,大租户迁到独立库,私有化客户独立部署。