MySQL直接用PARTITION BY RANGE易踩坑,因分区键必须是主键/唯一索引组成部分,否则报错;且分区裁剪仅对裸分区键的等值、范围条件生效,函数包装或类型转换会导致全分区扫描。

为什么直接用 PARTITION BY RANGE 容易踩坑
MySQL 的分区功能不是“开箱即用的加速器”,尤其对 InnoDB 表,分区键必须是主键或唯一索引的一部分,否则建表直接报错:ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function。这意味着你不能随便拿 create_time 分区,除非它已包含在主键里——而多数业务主键是自增 id,这就天然冲突。
常见错误是强行把 id 和 create_time 组成联合主键,结果破坏了插入性能(二级索引变大、写放大加剧),还让基于 id 的单行查询无法走分区裁剪。
- 真正可用的分区键,通常是业务上天然有序且高频过滤的字段,比如按天/月分片的
log_date或event_day - 若原表无该字段,宁可加列 + 建生成列(
ASvirtual column)+ 索引,也不要动主键结构 - 分区后
EXPLAIN PARTITIONS必须看到partitions列只显示少数几个分区名,否则等于没裁剪
如何让 SELECT ... WHERE create_time BETWEEN ... 真正命中分区
分区裁剪(partition pruning)只对 WHERE 中明确使用分区键的等值、范围或 IN 条件生效。像 WHERE DATE(create_time) = '2023-01-01' 这种函数包装,会导致全分区扫描——因为 MySQL 无法预判函数输出对应哪个分区。
正确写法必须裸用分区键:
SELECT * FROM orders_part WHERE create_time >= '2023-01-01 00:00:00' AND create_time < '2023-02-01 00:00:00';
- 确保查询条件和分区定义的表达式完全一致(比如都用
TO_DAYS()或都用YEARWEEK(),别混用) - 避免在分区键上做任何计算、类型转换或函数调用
- 如果业务常查“最近7天”,建议用
RANGE COLUMNS (create_time)而非RANGE (TO_DAYS(create_time)),减少隐式转换风险
ALTER TABLE ... REORGANIZE PARTITION 为什么卡住又失败
对大表执行分区 DDL(如合并旧分区、添加新分区)本质是重建数据,会锁表、耗 I/O、吃内存。InnoDB 在 8.0 之前不支持在线分区操作,即使加了 ALGORITHM=INPLACE,遇到大分区仍可能退化为 copy 表模式。
典型现象:语句长时间处于 Waiting for table metadata lock 或 copy to tmp table 状态,甚至因磁盘满失败。
- 提前检查
innodb_buffer_pool_size是否足够——至少应为待重组分区数据量的 1.5 倍 - 避免在业务高峰执行;优先用
REMOVE PARTITIONING+ 重建表(配合pt-online-schema-change)替代原地重组 - 新增分区务必用
ADD PARTITION预留未来窗口(如每月提前建好下3个月的分区),别等数据来了再补
分区表和普通表索引,性能差异到底在哪
分区本身不提速,它只是把一个大问题拆成多个小问题。真正起作用的是“每个分区独立维护自己的 B+ 树索引”。所以 idx_user_id 在分区表里其实是 N 个索引副本(每个分区一个),总索引体积更大,写入更慢,但单次查询若能精准落到 1–2 个分区,B+ 树深度更浅、缓存更热,响应更快。
但要注意:跨分区查询(如查所有用户近30天订单)会启动 N 个并发索引扫描,CPU 和磁盘 I/O 压力翻倍,此时可能比单表全索引扫描还慢。
- 高频单分区查询场景(如按天查某日日志)才适合分区;聚合类、宽时间范围查询慎用
- 复合索引必须把分区键放在最左,否则无法利用分区裁剪,例如分区键是
create_time,索引就得是(create_time, user_id),而非(user_id, create_time) - 定期用
ANALYZE TABLE更新各分区统计信息,否则优化器可能误判成本,选错执行计划
分区不是银弹。它解决的是“数据物理分布”问题,而不是“查询逻辑效率”问题。很多线上慢查,根源在缺失索引、SELECT *、JOIN 未走索引——这些不先清掉,加分区只会让问题更隐蔽。


















