分表是应对单表超500万行导致性能下降的必要手段,需区分水平分表(应用层路由)、分区(单库逻辑表)和垂直分表(拆分大字段),且须前置优化与归档评估。

分表不是为了炫技,是单表撑不住了
当一张表数据量超过 500万 行,尤其是达到 1000万+ 时,你会发现:加索引变慢、ALTER TABLE 基本卡死、SELECT ... WHERE x = ? 响应开始抖动,连 SHOW CREATE TABLE 都偶尔延迟。这不是 MySQL 故意使坏,而是底层 B+ 树索引深度增加、缓冲池(innodb_buffer_pool_size)命中率骤降、行锁/间隙锁竞争加剧的自然结果。更现实的信号是——运维同学开始抱怨备份超时、从库延迟飙升、慢查询日志里全是这张表。
水平分表 vs 分区:别把“逻辑一张表”当真
很多人以为用 PARTITION BY HASH(id) 就等于分表了,其实不然。分区仍是单库内的一张逻辑表,所有分区共享同一个表锁(InnoDB 下是 MDL 锁)、共用连接数、争抢同一块磁盘 IO。它适合冷热分离(比如按时间 RANGE 分区后快速 TRUNCATE PARTITION p_2024 归档),但扛不住高并发写入或跨分区 JOIN。而真正分表(如 order_001、order_002)必须由应用层路由,比如用用户 ID 取模:table_name = "order_" + (uid % 16)。好处是彻底解耦,坏处是你得自己处理 UNION ALL 查询、跨表事务、全局唯一 ID(不能依赖 AUTO_INCREMENT)。
垂直分表:大字段和高频字段必须分开
一张表里有 TEXT、BLOB、JSON 字段,又经常只查 id、name、status?这就是典型的垂直分表场景。把大字段单独拆到 user_extra 表,主表 user 留下核心字段。这样做不是为了减少行数,而是让每页(Page)能塞进更多记录,降低磁盘 I/O 次数。实操注意三点:
- 拆分后主键必须冗余(
user_extra.id是外键也是主键),否则 JOIN 成本更高 -
SELECT *会触发隐式 JOIN,务必在业务代码里显式控制是否加载扩展字段 - 更新大字段时,避免无谓地更新主表时间戳字段(如
updated_at),否则引发额外日志和锁
分表前先确认:你真的需要它吗?
很多团队跳过基础优化就急着分表,结果新表一样慢。务必先做三件事:
- 检查慢查询是否漏建索引,特别是
WHERE + ORDER BY + LIMIT组合,用EXPLAIN FORMAT=TREE看执行计划 - 确认没滥用
SELECT *、没在WHERE里对字段做函数操作(如WHERE DATE(create_time) = '2025-01-01') - 评估归档可行性:历史数据是否可迁出?能否用
pt-archiver定期清理?
GROUP BY、ORDER BY、分页(LIMIT 10000,20)都会变棘手,中间件(如 ShardingSphere)也救不了设计缺陷。
分表本身不难,难的是后续所有查询逻辑都得适配分片键;最容易被忽略的,是那个最初没想清楚的「非分片键查询」——比如用户要按手机号查订单,而你按用户 ID 分了表。










