分区表不是万能的,先看它解决什么问题
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虽快,但会触发一次完整的事务日志刷盘,高峰期可能拖慢写入。这些操作没法在线平滑完成。
