MySQL分区表仅在特定场景有效:单表超2000万行且范围查询频繁、需秒级删历史数据、写入热点明显;必须配合EXPLAIN PARTITIONS验证剪裁,避免非确定性表达式和函数使用,慎用复合分区,并注意分区数过多导致元数据操作变慢。

MySQL分区表不是“数据一多就上”的通用加速器,它只在几个明确场景下真正起效:单表持续超2000万行、查询固定带时间或业务维度条件、需要秒级删历史数据、写入热点明显且无法靠索引缓解。
单表行数长期超过2000万且存在范围查询
当 SELECT COUNT(*) 返回两千万以上,且常见查询是 WHERE created_at BETWEEN '2025-01-01' AND '2025-06-30' 这类范围扫描时,B+树深度增加,I/O放大严重。分区能将扫描限制在1–2个物理段内。
但注意:2000万 是经验阈值,不是硬指标。一张只有3个字段的用户表,5000万行可能仍很轻;而一张含JSON、TEXT字段的日志表,800万行就可能撑满buffer pool。
- 必须配合
EXPLAIN PARTITIONS验证是否发生分区剪裁(pruning)——输出中的partitions字段只显示目标分区名才算有效 - 避免用
DATE_SUB(NOW(), INTERVAL 30 DAY)这类非确定性表达式做分区键,会导致优化器无法剪裁 -
RANGE分区建议按月或按周切分,避免单个分区过大(如单月数据超5GB)或过小(如单日分区仅几千行)
需要高频删除过期数据(比如日志、行为埋点)
DROP PARTITION p202506 是元数据操作,毫秒级完成;而 DELETE FROM log_table WHERE dt 可能锁表几十分钟,还产生大量undo日志和碎片。
前提是你的归档策略与分区边界对齐:比如按 dt DATE 做 RANGE 分区,且每天/每月定时 ALTER TABLE ... DROP PARTITION。
- 不能对未定义的分区执行
DROP,得提前建好未来3–6个月的空分区(用REORGANIZE PARTITION动态扩展) - 如果业务要求“保留最近90天”,但分区是按月切的,那90天会跨3个分区,
DROP时需判断并清理多个分区 - 使用
LIST或HASH分区无法支持按时间批量清理,这类场景必须选RANGE或RANGE COLUMNS
写入热点集中且二级索引更新成为瓶颈
比如订单表所有新记录都集中在 created_at 最大值附近,导致最后一页buffer pool频繁争用、undo段膨胀、二级索引分裂卡顿。此时用 HASH 或 KEY 按 user_id 分区,能把写压力打散到多个物理段。
但反例也很典型:用自增 id 做 RANGE 分区,所有INSERT都挤在最后一个分区,等于没分。
-
HASH分区数建议设为2的幂(如4、8、16),避免取模后分布不均 - 分区键必须是主键或唯一索引的一部分,否则建表会报错
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function -
KEY比HASH更稳妥,它由MySQL内置哈希函数计算,对字符串等类型兼容性更好
查询条件中固定包含某个高选择性字段,但加索引效果差
例如慢查长期是 WHERE app_id = 123 AND event_time > '2025-05-01',给 (app_id, event_time) 加联合索引后 EXPLAIN 显示 rows 仍超百万——说明索引过滤率不够。这时把 app_id 当分区键,再在每个分区内建 event_time 索引,相当于先定位分区(逻辑子表),再走局部索引。
这种“分区 + 局部索引”组合比单一大索引更高效,但代价是:跨 app_id 的查询(如统计全平台数据)会变成 UNION ALL 多个分区扫描,性能反而下降。
- 分区键字段必须出现在
WHERE条件中,否则无法剪裁,查询会扫全表所有分区 - 不要在分区键上用函数,比如
WHERE YEAR(created_at) = 2025会让分区失效 - 复合分区(如
RANGE COLUMNS+SUBPARTITION BY HASH)调试成本高,线上环境慎用
最容易被忽略的一点:分区表的 INFORMATION_SCHEMA.PARTITIONS 表本身会变慢,当分区数超100个时,SHOW CREATE TABLE 和某些管理脚本响应明显延迟;同时备份工具(如 mysqldump)默认不感知分区结构,需手动加 --skip-triggers --no-create-info 配合 SELECT 导出,否则容易漏数据或锁死。


















