1. 独立 Schema 的适用场景
当你有"几十到几百个中大型租户",既想要强隔离、又不想维护几百个数据库实例时,独立 Schema 是甜点方案:同一 PostgreSQL 实例内kb_a.doc、kb_b.doc互相不可见,DDL 变更比独立库轻(拓扑见图 figure_05_1)。知识库场景下,每个租户的文档表、chunk 表、向量表各占一个 Schema,索引互相独立、互不影响。
2. 动态数据源路由设计
核心思路:一个"逻辑数据源"背后挂 N 个物理数据源(或同一库 N 个 Schema),运行时按tenantId决定路由到哪个(设计见图 figure_05_2)。Spring 的AbstractRoutingDataSource正好干这个——determineCurrentLookupKey()返回租户标识,框架据此选数据源。
3. Schema 初始化与 Flyway 多租户
新租户入驻要自动建 Schema 并跑迁移脚本。Flyway 本身不带多租户,需要循环每个 Schema 执行:
publicvoidinitTenantSchema(StringtenantId){Stringschema="kb_"+tenantId;jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS "+schema);Flywayflyway=Flyway.configure().dataSource(dataSource).schemas(schema).locations("db/tenant").load();flyway.migrate();// 在该 Schema 内建表/建向量索引}4. AbstractRoutingDataSource 落地
@BeanpublicDataSourcedataSource(DataSourceRegistryregistry){AbstractRoutingDataSourceds=newAbstractRoutingDataSource(){@OverrideprotectedObjectdetermineCurrentLookupKey(){returnTenantContext.get();// tenant-a / tenant-b}};ds.setTargetDataSources(registry.all());// Map<tenantId, DataSource>ds.setDefaultTargetDataSource(registry.defaultDs());returnds;}// 每个物理库用 search_path 指向 kb_<tenant> 的 Schema| 关注点 | 做法 |
|---|---|
| 路由键 | TenantContext.get() |
| Schema 切换 | SET search_path = kb_<tenant> |
| 连接池 | 每物理库独立池,避免某租户拖垮全局 |
| 新建租户 | 动态注册数据源 + 初始化 Schema |
5. 运维注意点
- 连接池隔离:每个物理数据源独立池,防止"大租户"占满连接拖垮其他租户。
- DDL 批量:Schema 多了,升级脚本要一次性批量跑完,做好失败回滚。
- 备份粒度:可按 Schema 单独备份,恢复单租户更快。
- search_path 注入:
kb_<tenant>同样要白名单校验,避免kb_a; DROP之类。
6. 小结
独立 Schema 用"动态数据源 + 按租户路由"在隔离与成本间取得平衡,是多租户知识库的常见落地形态。下一篇进入向量库本身的隔离——这是大模型场景最特殊、最容易踩坑的一层。