优先用 partition by range (to_days()),因其自动分区裁剪、运维成本低、边界清晰;手动分表易导致join/统计/ddl问题,且year()*100+month()会造成分区不连续和边界错误。

MySQL 按月分表该用 PARTITION BY RANGE 还是手动建表?
直接说结论:优先用 PARTITION BY RANGE (TO_DAYS()),别手动生成 orders_202401、orders_202402 这类表。手动分表看着灵活,实际运维成本爆炸,JOIN、统计、DDL 都会出问题。
分区表由 MySQL 内部管理,查询时能自动裁剪分区,WHERE order_time >= '2024-02-01' 会跳过 202401 及更早的分区;而手动分表必须靠应用层拼表名或 UNION ALL,一不小心就查全量。
- 分区键必须是整型或日期转整型(
TO_DAYS()或YEAR() * 100 + MONTH()),不能直接用DATE类型字段 - 分区不能包含主键以外的唯一索引——如果表有联合唯一约束,得把它加进主键里,否则
CREATE TABLE ... PARTITION BY直接报错 - MySQL 8.0+ 支持
ALTER TABLE ... REORGANIZE PARTITION拆分/合并分区,但 5.7 不支持动态新增未来分区,得提前写好脚本每月执行
为什么用 TO_DAYS() 而不是 YEAR() + MONTH()?
因为 TO_DAYS() 生成连续整数,分区边界清晰可控;而 YEAR(order_time)*100 + MONTH(order_time) 算出来是 202401、202402 这种,看着直观,但会导致分区不连续——比如 202413 就非法,下个月边界难定义,VALUES LESS THAN 容易写错。
典型错误是写成 VALUES LESS THAN (202402),结果 2024-01-31 的数据被分到 202401 分区,但 2024-02-01 到 2024-02-29 却进了 202402 分区——看似按月,实则按“年月字符串”切,漏掉跨月边界。
- 正确写法:
PARTITION BY RANGE (TO_DAYS(order_time)) (PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01'))) -
TO_DAYS('2024-02-01')返回 739252,是个确定整数,不会因格式歧义出错 - 注意:分区表达式里不能用函数调用以外的计算,
TO_DAYS(order_time) + 0这种绕过也不行,MySQL 会拒绝建表
查询变慢?检查是否触发了全分区扫描
分区裁剪失效很常见,一旦 WHERE 条件没覆盖分区键,或者用了函数包装(如 WHERE DATE(order_time) = '2024-02-01'),MySQL 就会扫所有分区。
用 EXPLAIN PARTITIONS 看实际访问了哪些分区:partitions: p202401,p202402,p202403 是正常;partitions: p202401,p202402,...,p202512 就是崩了。
- 避免在分区键上用函数、类型转换、隐式转换(比如把
order_time和字符串比较) - 范围查询要够“实”,
WHERE order_time BETWEEN '2024-02-01' AND '2024-02-29'能裁剪,但WHERE order_time > NOW() - INTERVAL 30 DAY在预编译阶段无法确定分区,可能扫多 - 分区表的
COUNT(*)比普通表慢——它得合并各分区计数,别拿它做实时总条数统计
删除历史数据时,别用 DELETE,要用 DROP PARTITION
按月存三年数据,删 2022 年前的分区,用 ALTER TABLE orders DROP PARTITION p202201, p202202,秒级完成;用 DELETE FROM orders WHERE order_time ,不仅锁表久,还会留下大量碎片,后续 INSERT 性能下降。
但要注意:DROP PARTITION 会直接丢数据,不可回滚;且不能删最后一个分区(MySQL 报错 ERROR 1507 (HY000): Cannot remove all partitions),得留一个空的“归档分区”兜底。
- 提前建好未来 6 个月的分区,用脚本每月
REORGANIZE拆新分区,避免高峰期卡住 - 如果业务要求保留冷数据但不参与查询,可用
ALTER TABLE ... EXCHANGE PARTITION把分区导出到普通表,再DROP - 分区表不支持外键,如果原表有外键约束,迁移前必须先删掉,否则建分区表失败
分区不是银弹。当单表数据量没到千万级、查询条件又很少按时间过滤时,加个 order_time 索引比搞分区实在得多。真上了分区,最麻烦的其实是监控和告警——没人会记得某个月分区突然没更新,直到某天查不到上月数据才翻车。











