如何创建复合分区表_RANGE-HASH或RANGE-LIST多级分区策略组合

2次阅读

mysql 8.0+ 创建 RANGE-HASH 复合分区表需先按 RANGE COLUMNS 列分区,再对每个分区统一声明相同数量的 HASH 子分区,子分区键须为确定性整型表达式且 SUBPARTITIONS 数量必须一致。

MySQL 8.0+ 创建 RANGE-HASH 复合分区表的关键写法

mysql 原生不支持 range-hash 这种两级分区语法(比如 partition by range columns(...) subpartition by hash(...) 是合法的,但必须显式写出子分区数量或定义),很多人卡在语法报错或数据只落到第一个分区上。

真正能用的写法是:先按列做 RANGE 分区,再对每个 RANGE 分区手动声明若干 HASH 子分区,且所有子分区总数必须一致。否则会报错 Error 1517 (HY000): Duplicate partition name 或直接拒绝建表。

  • PARTITION BY RANGE COLUMNS(created_at) 必须指定具体列,不能用表达式;created_at 类型要是 date/DATETIME/timestamp,否则分区裁剪失效
  • 每个 PARTITION p202401 后必须跟 SUBPARTITION BY HASH(YEAR(created_at)*100+MONTH(created_at)) SUBPARTITIONS 4 —— 注意 SUBPARTITIONS 4 要统一,不能有的写 4、有的写 8
  • 子分区键只能是单列或确定性表达式,HASH 不支持 TO_DAYS() 这类非整型返回值,会报 ERROR 1064
CREATE table orders (   id BIGINT,   created_at DATETIME,   amount DECIMAL(10,2) ) PARTITION BY RANGE COLUMNS(created_at) (   PARTITION p202401 VALUES LESS THAN ('2024-02-01')      SUBPARTITION BY HASH(YEAR(created_at)*100+MONTH(created_at)) SUBPARTITIONS 4,   PARTITION p202402 VALUES LESS THAN ('2024-03-01')      SUBPARTITION BY HASH(YEAR(created_at)*100+MONTH(created_at)) SUBPARTITIONS 4,   PARTITION p_future VALUES LESS THAN (MAXVALUE)      SUBPARTITION BY HASH(YEAR(created_at)*100+MONTH(created_at)) SUBPARTITIONS 4 );

postgresql 没有原生 RANGE-LIST 复合分区,得靠继承 + 触发器模拟

PostgreSQL 12+ 支持声明式分区,但只允许一级分区(PARTITION BY LISTRANGE),没有 SUBPARTITION 关键字。想实现“按年 RANGE、再按地区 LIST”,就得手动建子表 + 触发器路由。

常见翻车点:触发器函数里漏写 RETURN NULL,导致主表也能插数据;或者 INSERT ... select 批量插入时没走触发器,数据全进主表空壳里。

  • 主表用 PARTITION BY RANGE (order_date),每个年份建一个子分区表(如 orders_2024
  • 再对 orders_2024 单独执行 CREATE TABLE orders_2024_beijing PARTITION OF orders_2024 for VALUES IN ('BJ')
  • 触发器函数里必须用 if NEW.region = 'BJ' THEN INSERT INTO orders_2024_beijing VALUES (NEW.*); RETURN NULL; —— 少了 RETURN NULL,主表还会多存一份
  • 批量导入用 copyINSERT INTO ... SELECT 时,触发器不生效,得改用 INSERT ... ON CONFLICT 或分批次调用

oracle 12c+ 的 INTERVAL-RANGE + LIST 子分区实际限制

Oracle 看似最友好:PARTITION BY RANGE (dt) INTERVAL (NUMTOYMINTERVAL(1,'MONTH')) SUBPARTITION BY LIST (region),但真实场景中容易忽略两个硬约束:

  • 子分区模板(SUBPARTITION TEMPLATE)必须提前定义全,比如写了 (SUBPARTITION sp_bj VALUES ('BJ'), SUBPARTITION sp_sh VALUES ('SH')),那新生成的每月分区就只会创建这两个子分区,不会自动适配新增的 'SZ'
  • INTERVAL 分区只支持 NUMTODSINTERVALNUMTOYMINTERVAL,不能用 TRUNC(dt, 'MM') 这种函数,否则建表直接报 ORA-14751
  • 查询时如果 WHERE dt BETWEEN ... AND ... AND region = 'SZ',而 SZ 不在模板里,就会全表扫描所有子分区,性能断崖下跌

补救办法只有手工 ALTER TABLE ... ADD PARTITION ... SUBPARTITIONS,没法靠 INTERVAL 自动扩展子分区维度。

为什么 RANGE-HASH 在 tidb / oceanbase 上基本不用

TiDB 5.4+ 和 OceanBase 4.x 都明确文档标注:不推荐、不测试、不保证稳定性。它们的分区机制底层依赖 Region 拆分,而 HASH 子分区会打乱数据局部性,导致跨 Region 查询暴增,QPS 掉 30% 以上是常态。

实测发现,即使语法能过(TiDB 允许 PARTITION BY RANGE ... SUBPARTITION BY HASH),执行 EXPLAIN 也会显示 PartitionUnion 扫描全部子分区,等于没分区。

  • 真正该用的是 PARTITION BY RANGE COLUMNS 单级,配合合理的 PRIMARY KEY 前缀(比如 (order_id, order_date)),让 TiKV Region 按时间天然切分
  • OceanBase 推荐用 PARTITION BY KEY 替代 HASH,因为 KEY 支持多列且更均匀;RANGE-LIST 组合则必须用 LIST COLUMNS 显式声明,LIST (region) 会因字符集问题报 OB_ERR_INVALID_ARGUMENT
  • 所有分布式数据库里,“复合分区”本质是权衡:换一点 DDL 复杂度,省下后期手动 ALTER TABLE ... REORGANIZE PARTITION 的运维成本 —— 但这个成本往往比预想中高得多

最常被跳过的一步:没在业务低峰期验证 SELECT count(*) 是否真的下推到各子分区。只要 EXPLAIN 显示 type: ALL 且没出现 partitions: 列,说明分区策略根本没生效。

text=ZqhQzanResources