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

直接说结论:用 PARTITION BY RANGE (TO_DAYS()) 按时间字段分区,是当前最稳妥、剪枝最可靠、运维成本最低的方案;其他如 YEAR()*100+MONTH() 或 UNIX_TIMESTAMP() 容易踩边界错误或时区坑,不建议在生产环境使用。
为什么 WHERE create_time >= '2024-01-01' 还会全表扫描?
分区裁剪(partition pruning)不是自动生效的,它依赖三个硬性条件同时满足:
- 查询条件中必须**直接引用分区键**,不能包裹函数 ——
WHERE YEAR(create_time) = 2024或WHERE DATE_FORMAT(create_time, '%Y') = '2024'都会导致partitions: NULL - 分区键(如
create_time)**必须包含在主键或任意唯一索引中** —— 否则 MySQL 5.7+ 会建表失败,低版本虽能建但查询不稳定 - 查询语句里**必须出现该字段的等值或范围条件** ——
WHERE user_id = 123却按create_time分区,就会扫所有分区,还多一层元数据开销
验证是否生效,唯一可信方式是:EXPLAIN PARTITIONS SELECT * FROM order_history WHERE create_time >= '2024-01-01',看 partitions 列是否只列出几个分区名(如 p202401,p202402),而不是 all 或空。
建表时 TO_DAYS() 分区怎么写才不出错?
TO_DAYS() 是把日期转为整数,但边界值必须对齐自然月起始日,否则建表报错或分区逻辑错乱:
- 错误写法:
LESS THAN (TO_DAYS('2024-01'))→ 报错,因为'2024-01'不是合法日期字面量 - 更危险的错误:
LESS THAN (TO_DAYS('2024-01-31'))→ 实际表示“2024-01-31 之前”,导致 2024-01-31 00:00:00 的数据进不了该分区 - 正确写法:
LESS THAN (TO_DAYS('2024-02-01'))→ 表示“2024 年 1 月及之前所有日期”,覆盖完整自然月 - 必须预留未来分区:
PARTITION p_future VALUES LESS THAN MAXVALUE,否则插入 2026-07 数据会触发ERROR 1526 (HY000): Table has no partition for value
主键也得配合改:不能只有 PRIMARY KEY (id),必须是联合主键,例如 PRIMARY KEY (id, create_time)。
查历史数据加 ORDER BY + LIMIT 为啥反而变慢?
因为 MySQL 要先从每个匹配分区中各自取出足够多的数据(满足 LIMIT 所需数量),再合并、排序、截断。如果查的是跨 6 个月的数据,就得从 6 个分区各取前 N 条,再做归并 —— IO 和 CPU 开销翻倍。
优化思路不是禁用 ORDER BY,而是:
- 确保
ORDER BY字段有索引,且该索引以分区键为前导列(如(create_time, updated_at)) - 避免大偏移:用游标分页(
WHERE create_time > ? AND id > ?)替代OFFSET - 若只需最新几条,可先用子查询缩小分区范围:
SELECT * FROM order_history WHERE create_time >= '2026-06-01' ORDER BY create_time DESC LIMIT 10
最常被忽略的一点:分区本身不解决索引设计问题。即使分区剪枝成功,每个分区内部仍要走索引;如果没建对索引,单分区扫描也会很慢。











