mysql中分区表的使用与索引优化

MySQL分区表不提升单条点查性能,仅优化批量删旧、范围扫描和减少锁竞争;分区键须含于主键,否则索引失效;仅支持本地索引,跨分区查询性能更差。

分区表不是万能的,先看它解决什么问题

MySQL 分区表本质是把一张大表按规则拆成多个物理子表(ENGINE=InnoDB 下仍是独立 .ibd 文件),但对应用层仍表现为一张表。它**不减少索引大小,也不自动提升单条查询性能**,主要价值在:批量删旧数据(如 DROP PARTITION)、加速范围扫描(如按时间查某几个月)、减少锁竞争(不同分区可并发操作)。如果你的瓶颈是 WHERE user_id = ? 这类点查,分区几乎没用——这时候该优化的是二级索引或主键设计。

选对分区键,否则索引会失效

分区键必须是主键或唯一索引的一部分,否则建表直接报错:ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function。这意味着:如果按 created_at 分区,主键就必须包含 created_at;若想保留 id 为主键,就得改成联合主键 (id, created_at)。后果很实际:原本 WHERE id = 123 走主键索引,加了分区后可能变*分区扫描,因为 MySQL 无法确定该记录落在哪个分区。

  • 推荐场景:按时间字段(DATE/DATETIME)做 RANGELIST 分区,且业务查询天然带时间范围(如 WHERE created_at BETWEEN '2025-01-01' AND '2025-06-30'
  • 避免场景:按 user_id % 100HASH 分区,同时又高频查 WHERE order_no = ? ——除非 order_no 也在分区表达式里,否则每个分区都得查一遍
  • 注意 TO_DAYS()YEAR() 的兼容性:前者支持到 2100 年左右,后者在跨年边界容易出错(比如 YEAR('2025-12-31')YEAR('2025-01-01') 不同,但分区边界若设为 VALUES LESS THAN (2025),会导致 2025 年 1 月数据进错区)

分区表上的索引,到底是本地还是全局

MySQL 只支持 本地索引(LOCAL,即每个分区维护自己的 B+ 树。没有真正的全局索引。这意味着:跨分区查询(如 WHERE status = 'paid')必然触发所有分区的索引查找,再合并结果。性能不会比不分区好,甚至更差(多了一层分区路由开销)。所以:

  • 别指望靠分区表 + 普通二级索引解决高并发点查问题
  • 如果必须按非分区字段查,优先考虑覆盖索引(INDEX(status, created_at, amount)),让索引本身包含所需字段,避免回表
  • PRIMARY KEY 必须含分区键 → 实际上强制你把时间维度“嵌入”主键,这会影响插入顺序和页分裂,尤其当 created_at 非单调时(如补录历史数据)

执行计划里看到 partition 字段,不代表就高效

EXPLAIN PARTITIONS 看执行计划时,partitions 列显示实际访问的分区名(如 p202501,p202502),这只是说明 MySQL 成功剪枝了无关分区。但关键要看 typekey如果 typeALLindex,说明这个分区内部仍在全扫;如果 keyNULL,说明连本地索引都没用上。常见陷阱:

EXPLAIN PARTITIONS SELECT * FROM orders WHERE created_at > '2025-03-01' AND status = 'shipped';

即使只访问 p202503,p202504 两个分区,若 status 上无索引,每个分区仍要全表扫描。正确做法是建复合索引:INDEX(created_at, status)(注意顺序:分区键在前,才能被用于分区剪枝 + 索引下推)。

真正容易被忽略的是分区维护成本:新增分区要 ALTER TABLE ... REORGANIZE PARTITION,大表可能锁表数分钟;DROP PARTITION 虽快,但会触发一次完整的事务日志刷盘,高峰期可能拖慢写入。这些操作没法在线平滑完成。