不可行。MySQL 8.0+ 的 LIST 分区仅支持显式枚举值,不支持表达式或动态列值映射,tenant_id 作为动态增长主键无法预先穷举,频繁 ALTER PARTITION 导致运维不可行。
MySQL 8.0+ 按 tenant_id 做 LIST 分区是否可行?
不行。mysql 的 list 分区只支持显式枚举值,不支持表达式或列值范围映射;tenant_id 是动态增长的业务主键,无法预先穷举所有可能取值,硬写成 values in (1,2,3,...) 会随租户增加频繁重建分区,运维不可行。
常见错误现象:ERROR 1659 (HY000): Field 'tenant_id' is of a not allowed type for this type of partitioning(当误用非整型或尝试表达式时);或建表成功但插入新租户数据时报 ERROR 1526 (HY000): Table has no partition for value ...。
- LIST 分区要求每个分区必须明确列出所有允许的
tenant_id值,不支持VALUES IN (SELECT ...)或函数计算 - 即使初期只有 10 个租户,新增第 11 个租户就必须
ALTER TABLE ... REORGANIZE PARTITION,锁表时间随数据量线性增长 - MySQL 官方文档明确将 LIST 归类为“静态分区策略”,适用于状态码、地区编码等有限、稳定集合
PostgreSQL 中用 tenant_id 实现原生分区并绑定表空间
可行,且是当前最贴近需求的方案。PostgreSQL 12+ 支持基于 tenant_id 的声明式 RANGE/LIST 分区,并允许每个分区独立指定 TABLESPACE,天然适配多租户物理隔离。
使用场景:租户数量中等(几百到几千)、租户数据量差异大、需按租户做备份/迁移/限流。
- 必须先创建表空间:
CREATE TABLESPACE ts_tenant_123 LOCATION '/data/pg_tbs/tenant_123',路径需被 PostgreSQL 用户读写且不在主数据目录下 - 父表不能有主键(否则子表无法继承),改用
UNIQUE (tenant_id, id)+NOT NULL tenant_id约束替代 - 分区键必须是
tenant_id单列,类型为int或bigint;若业务用 UUID,需额外加一列tenant_id_int做哈希映射(如hashtext(lease_tenant_id) % 10000) - 示例建表语句片段:
CREATE TABLE orders ( id bigint, tenant_id int NOT NULL, amount numeric ) PARTITION BY LIST (tenant_id); CREATE TABLE orders_t123 PARTITION OF orders FOR VALUES IN (123) TABLESPACE ts_tenant_123;
Oracle 12c+ 用 LIST 分区 + TABLESPACE 的实际限制
语法上支持,但工程落地代价高。Oracle 允许 LIST 分区指定表空间,但每个分区仍需显式定义,且无法自动创建新分区——租户上线必须人工执行 ALTER TABLE ... ADD PARTITION,无自动 fallback 机制。
性能影响:当租户数超 500,分区元数据查询(如 DBA_TAB_PARTITIONS)明显变慢;大量小分区还会加剧 buffer cache 碎片化。
- 不能用
DEFAULT分区兜底,否则违反“严格按租户隔离”前提(不同租户数据混入同一分区) -
tenant_id若为字符串类型,LIST 分区对大小写、空格、NULL 敏感,易因数据清洗不一致导致插入失败 - 备份恢复时,
RMAN不支持单个分区跨表空间增量备份,必须整库或整个表空间级操作
更现实的替代路径:逻辑隔离 + 表空间软约束
放弃“每个租户一张物理分区”的执念,改用统一分区策略(如按时间 RANGE 分区)+ 应用层强制 WHERE tenant_id = ? 过滤 + 表空间按租户分组管理,平衡可维护性与隔离强度。
关键点在于把隔离责任从数据库引擎下沉到 ORM 和中间件:
- 在应用 DAO 层统一拦截 SQL,自动注入
AND tenant_id = ?,避免漏写条件;MyBatis 可用@SelectProvider封装,Spring Data JPA 可用@Query模板 - 表空间不按单租户划分,而是按租户等级分组:例如
ts_premium(VIP 租户共用)、ts_basic(普通租户共用),降低运维粒度 - 审计重点不是“物理是否隔离”,而是“SQL 是否带 tenant_id 条件”和“索引是否覆盖 tenant_id + 查询字段”,后者直接影响查询性能和越权风险
真正难的不是建多少个分区,而是确保每次查询都走对索引、每条 DML 都带上租户上下文、每个备份恢复流程都验证租户数据边界——这些靠数据库语法解决不了,得靠代码规范和 CI 检查卡点。










