初期应选共享库+独立Schema,禁用tenant_id字段隔离和独立数据库。因字段隔离依赖人工加WHERE条件,易漏导致数据泄露;独立数据库运维成本过高;独立Schema在同实例内仅增命名空间,天然阻断跨租户查询,管理成本低且支持动态生成、按Schema备份。

初期选共享库 + 独立 Schema,别碰共享表(tenant_id 字段隔离),也先别上独立数据库——成本和复杂度不匹配当前阶段的真实需求。
为什么不能直接用 tenant_id 字段隔离?
字段隔离看着最省事,但它的安全边界完全依赖开发写每条 SQL 时都手动加 WHERE tenant_id = ?。漏一次,数据就串;测试环境还大概率测不出来,因为数据量小、查询结果碰巧没重叠。你见过报表导出时漏条件导致全租户客户名单泄露的事故吗?就是这么来的。
初期团队小、迭代快、人手紧,指望靠“自觉”守住数据边界,等于把门锁换成贴纸。
- 所有 MyBatis 查询、JPA
@Query、原生 JDBC、甚至定时任务里的SELECT *,都得人工检查是否带租户条件 - 第三方库或动态 SQL(比如拼接
ORDER BY)极易绕过拦截器,TenantInterceptor拦不住 - MySQL 不支持行级安全策略(RLS)开箱即用,得靠企业版或 PostgreSQL 才能补这个缺口
为什么推荐独立 Schema 而不是独立数据库?
独立数据库物理隔离最强,但初期 10 个租户就配 10 套 MySQL 实例?运维监控、备份策略、主从同步、权限管理全要翻倍,DBA 都得配两个。而独立 Schema 在同一个 MySQL 实例里,只多一层命名空间,管理成本几乎不增加,却能天然阻断跨租户查询——SELECT * FROM users 默认查不到 tenant_2.users,除非显式写成 tenant_2.users,这种写法在业务代码里根本不会出现。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- Schema 名可动态生成,如
tenant_123或acme_prod,便于识别和清理 - Hibernate 的
MultiTenantConnectionProvider、MyBatis Plus 的DynamicTableNameParser都能平滑支持 - 备份恢复可按 Schema 粒度执行,不影响其他租户,比单库更可控
- 注意:MySQL 5.7+ 对 Schema 数量无硬限制,但超 500 个后需关注
information_schema查询性能
怎么落地独立 Schema 方案?三件事必须做死
光切 Schema 不够,租户上下文必须贯穿全程,否则中间件路由会失效。
- 租户识别入口统一收口:只允许从请求头(如
X-Tenant-ID)、子域名(acme.yoursaas.com)或 JWT payload 里取,禁止从 URL 参数或表单字段读 —— 后两者太容易被伪造或忽略 - 上下文必须绑定到线程(
ThreadLocal)且不可变:一旦进入TenantContext.setCurrentTenant("acme"),整个请求链路不能再改;异步线程(如CompletableFuture、线程池)要显式传递,否则子线程里TenantContext.getCurrentTenant()为空 - 数据库连接层强制校验:在
getConnection()之后立刻执行connection.setSchema(tenantId),并捕获SQLException(如 Schema 不存在时抛ER_BAD_DB_ERROR),立即告警而不是静默 fallback 到 public
最容易被忽略的四个漏点
Schema 隔离看似简单,但真正上线后出问题的地方,往往不在主流程里。
-
flyway或liquibase迁移脚本默认作用于publicSchema,必须配置flyway.schemas=tenant_123,否则新表建在错的地方 - Redis 缓存 key 必须带租户前缀,比如
cache:tenant_123:user:1001,否则缓存穿透会跨租户返回脏数据 - 消息队列(如 Kafka/RabbitMQ)消费端处理时,如果消息体没带租户标识,就得从消息头或路由规则里补全,否则
tenant_123的订单事件可能被tenant_456的监听器处理 - 数据库连接池(如 HikariCP)的
connectionInitSql如果写了USE tenant_xxx,会导致连接复用时 Schema 错乱 —— 应该在每次获取连接后由框架动态 setSchema,而不是初始化时固化
Schema 隔离的脆弱点不在设计,而在那些“看起来跟租户无关”的角落。初期人力有限,优先堵住这四类场景,比反复优化主流程更有效。










