MySQL 8.0+复合分区必须用RANGE COLUMNS且主子分区需基于同一列,不支持函数混用;PG仅支持一级声明式分区;Oracle虽支持INTERVAL+子分区模板但需手动预定义31个边界;推荐按天建表+应用路由。
MySQL 8.0+ 复合分区必须用 RANGE COLUMNS,不能混用 RANGE 和日期函数
mysql 不支持在子分区中直接写 to_days() 或 year() 等函数表达式——这是最常卡住的地方。你写 partition by range (to_days(date_col)) subpartition by range (year(date_col)) 会直接报错 error 1064。
正确做法是:主分区和子分区都基于同一列(或列组合),且必须用 RANGE COLUMNS,它允许按日期列原值比较,不依赖函数:
CREATE TABLE logs (
id BIGINT,
event_time DATETIME
)
PARTITION BY RANGE COLUMNS(event_time)
SUBPARTITION BY RANGE COLUMNS(event_time) (
PARTITION p202401 VALUES LESS THAN ('2024-02-01'),
PARTITION p202402 VALUES LESS THAN ('2024-03-01'),
...
);- 主分区按「天」切分?不行——
RANGE COLUMNS只接受离散的、明确的边界值,不能自动展开每天;你得手动写每月的起始/结束边界 - 想实现「按天分区 + 按月子分区」,本质是二维切分:主分区控制粒度(比如按月),子分区再按天细分——但 MySQL 子分区只能是
HASH或KEY,不支持二级RANGE - 所以真正能落地的方案是:主分区按月(
RANGE COLUMNS),子分区用HASH均匀打散,再靠应用层或分区裁剪逻辑模拟“按天”访问
PostgreSQL 的 CREATE TABLE ... PARTITION BY RANGE 不支持子分区嵌套
PG 12+ 支持声明式分区,但只有一级:你可以按 event_time 按月分区,再在每个月分区里手动建按天的子表并 INHERIT,但这不是原生子分区,而是表继承模拟。
常见错误是试图写:
CREATE TABLE logs_y2024m01 PARTITION OF logs
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01')
PARTITION BY RANGE (event_time); -- ❌ 语法错误PG 不允许在 PARTITION OF 后再加 PARTITION BY。可行路径只有:
- 一级按月分区(用
RANGE) - 每月分区下,用触发器或应用逻辑把数据 INSERT 到对应日表(如
logs_y2024m01_d01) - 或者用
pg_partman扩展,它通过后台任务自动管理日级子表,并统一用UNION ALL视图对外暴露
Oracle 的 INTERVAL 分区 + SUBPARTITION TEMPLATE 是唯一接近需求的原生方案
Oracle 11g+ 支持主分区按月自动扩展(INTERVAL NUMTOYMINTERVAL(1, 'MONTH')),同时用模板定义子分区按天切分:
CREATE TABLE sales (
id NUMBER,
sale_date DATE
)
PARTITION BY RANGE (sale_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
SUBPARTITION BY RANGE (sale_date)
SUBPARTITION TEMPLATE (
SUBPARTITION sp_01 VALUES LESS THAN (TO_DATE('2024-01-02', 'YYYY-MM-DD')),
SUBPARTITION sp_02 VALUES LESS THAN (TO_DATE('2024-01-03', 'YYYY-MM-DD')),
...
)
(
PARTITION p_init VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);注意:
- 子分区模板只对后续自动生成的分区生效,初始分区
p_init不会被自动拆成天级子分区 - 模板里必须写死每一天的边界,无法用循环生成——意味着你要预生成 31 个
SUBPARTITION定义,哪怕某月只有 30 天 - 查询时若条件带
sale_date = DATE '2024-01-15',Oracle 能精准定位到p_202401.sp_15,但统计信息可能不准,需定期DBMS_STATS.GATHER_TABLE_STATS
真正要「按天分区、按月管理」,多数场景该放弃复合分区,改用时间戳前缀表名 + 应用路由
像 logs_20240101、logs_20240102… 这种命名,配合一个路由函数决定往哪张表写,比硬搞数据库复合分区更可控。
原因很实际:
- MySQL/PG/Oracle 对多级范围分区的支持要么残缺,要么维护成本高(比如 Oracle 模板要手写 31 份,新增月份还得改 DDL)
- 按天建表后,删旧数据就是
DROP TABLE logs_20231201,毫秒级;而删分区可能锁表、触发元数据刷新 - 备份恢复可按月打包(
mysqldump logs_202401*),不用解析复杂分区定义 - 唯一多出的成本是应用层多一个路由判断,但几行代码就能搞定:
table_name = f"logs_{dt.strftime('%Y%m%d')}"
复合分区听着高级,但只要业务允许表名带日期,就别在分区语法里反复碰壁——边界定义、自动扩展、统计信息、跨分区查询性能,全是隐性坑。










