MySQL 8.0+ 支持 RANGE COLUMNS 按月分区,但需手动执行 ADD PARTITION,无法自动创建;必须用确定性函数(如 YEAR/MONTH)和 DATE/DATETIME 字段,且 VALUES LESS THAN 应设为下月首日(如 '2024-06-01'),避免数据丢失。
MySQL 8.0+ 直接支持 RANGE COLUMNS,但按月自动分区得自己维护
mysql 原生不提供“按月自动创建新分区”的能力,alter table ... add partition 必须手动执行。所谓“自动”,实际是靠外部脚本或事件调度器定期触发。如果你刚建表就指望 mysql 自己每月初生成 p202404、p202405 这类分区,会发现根本没这功能。
常见错误现象:ERROR 1486 (HY000): Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed —— 想用 NOW() 或 CURDATE() 当分区表达式?不行,必须是确定性函数。
- 只能用
TO_DAYS()、YEAR()、MONTH()等确定性函数,且字段必须是DATE/DATETIME类型(不能是字符串) -
RANGE COLUMNS允许直接用日期列(如created_at),比老式RANGE+TO_DAYS()更直观 - 分区上限受
max_partition_count限制(默认 8192),但真实瓶颈常是ALTER TABLE加锁时间,尤其大表
ADD PARTITION 脚本要避开月末边界和时区陷阱
手动加分区看似简单,但 VALUES LESS THAN 的值写错一小时,就可能漏数据或报错。比如想建 2024 年 5 月分区,终点该设成 '2024-06-01',不是 '2024-05-31' —— 后者会让 '2024-05-31 14:22:03' 这类带时间的数据无处可去。
使用场景:通常在每月 1 号凌晨执行一次脚本,提前建好下个月分区(避免当月最后几小时写入失败)。
- 正确写法:
ALTER TABLE orders ADD PARTITION (PARTITION p202406 VALUES LESS THAN ('2024-06-01')); - 错误写法:
VALUES LESS THAN (TO_DAYS('2024-05-31'))—— 如果表用的是RANGE COLUMNS,类型不匹配直接报错 - 时区注意:确保 MySQL 服务器时区(
SELECT @@time_zone)和业务写入时间一致,否则'2024-06-01'在 UTC 时区下可能比本地早/晚一天
PostgreSQL 用户别被误导:它压根没有 PARTITION BY RANGE 的原生月分区语法
PostgreSQL 10+ 支持声明式分区,但 CREATE TABLE ... PARTITION BY RANGE (created_at) 只定义规则,每个分区仍需显式 CREATE TABLE ... FOR VALUES FROM ('2024-04-01') TO ('2024-05-01')。没有 ADD PARTITION 命令,也没有自动机制。
性能影响:分区键必须是查询条件的一部分,否则优化器可能全表扫描所有分区;另外,INSERT 会额外消耗 CPU 判断路由到哪个子表。
- 必须为每个分区单独建索引,
CREATE INDEX ON orders_202404 (user_id)不能省 - 删除旧分区用
DROP TABLE orders_202301最快,比DELETE高效得多,但要注意外键和依赖视图 - 如果用
pg_partman扩展,它本质也是定时跑CREATE TABLE ... FOR VALUES+ATTACH PARTITION,底层没魔法
真正省事的方案:用时间戳字段 + 应用层路由,而不是强依赖数据库分区
很多团队卡在“一定要数据库自动分区”这个点上,结果花两周调脚本、修时区、处理锁表,而业务增长根本没到单表瓶颈。其实 90% 的中等规模系统,用 WHERE created_at >= '2024-04-01' AND created_at 配合普通 B-tree 索引,性能完全够用。
容易被忽略的地方:分区表的 EXPLAIN 输出里,如果看到 type: ALL 或 partitions: all,说明分区裁剪失效——大概率是 WHERE 条件没用上分区键,或者用了函数包裹(如 DATE(created_at))。
- 检查裁剪是否生效:
EXPLAIN PARTITIONS SELECT * FROM orders WHERE created_at = '2024-04-15'; - 分区键上不要建冗余索引,比如已有
(created_at, user_id),再单独建created_at索引就是浪费 - 如果真要自动化,Python +
mysql-connector-python写个 20 行脚本,每月 1 号 cron 调用,比啃官方文档快得多

















