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

来源:这里教程网 时间:2026-02-28 20:39:23 作者:

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

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
)做
RANGE
LIST
分区,且业务查询天然带时间范围(如
WHERE created_at BETWEEN '2024-01-01' AND '2024-06-30'
避免场景:按
user_id % 100
HASH
分区,同时又高频查
WHERE order_no = ?
——除非
order_no
也在分区表达式里,否则每个分区都得查一遍
注意
TO_DAYS()
YEAR()
的兼容性:前者支持到 2100 年左右,后者在跨年边界容易出错(比如
YEAR('2024-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
列显示实际访问的分区名(如
p202401,p202402
),这只是说明 MySQL 成功剪枝了无关分区。但关键要看
type
key
如果
type
ALL
index
,说明这个分区内部仍在全扫;如果
key
NULL
,说明连本地索引都没用上
。常见陷阱:

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

即使只访问

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

真正容易被忽略的是分区维护成本:新增分区要

ALTER TABLE ... REORGANIZE PARTITION
,大表可能锁表数分钟;
DROP PARTITION
虽快,但会触发一次完整的事务日志刷盘,高峰期可能拖慢写入。这些操作没法在线平滑完成。

相关推荐