当业务里的某张核心表行数突破千万甚至上亿,查询延迟和写入阻塞会明显上升。面对单表数据量过大的问题,团队常纠结该选分库还是分表。这两种方案解决的瓶颈并不完全相同,盲目选择反而会增加后期维护成本。

分库与分表的核心差异
分表是指在一个数据库实例内,将原本的一张大表按规则拆成多张结构相同的子表,例如按用户ID取模散列到 order_0 到 order_7。分库则是把数据分布到不同的数据库实例,可能同时配合分表形成分库分表。
| 维度 | 分表 | 分库 |
|---|---|---|
| 物理资源 | 单实例内 | 多实例多机器 |
| 单机瓶颈 | 仍存在 | 可横向扩展 |
| 跨片查询 | 实例内跨表 | 跨实例网络开销 |
| 事务成本 | 本地事务 | 需分布式事务 |
什么情况分表就够了
如果监控显示数据库实例的CPU和内存使用率并不高,只是单表数据行太多导致B+树层级变深、查询变慢,那么分表可以有效降低单表数据量。下面是一段基于用户ID分表的简单路由逻辑:
// 根据 userId 计算落在哪张订单子表
public String getTableName(long userId) {
int tableCount = 8;
long index = userId % tableCount;
return "order_" + index;
}
// 插入订单时选择对应表
public void insertOrder(long userId, String detail) {
String table = getTableName(userId);
String sql = "INSERT INTO " + table + " (user_id, detail) VALUES (?, ?)";
// 执行 sql 逻辑
}
这种方式运维简单,不需要引入新的数据库中间件,但单机磁盘IO和连接数上限依旧是天花板。
什么情况必须分库
当单实例磁盘已经写满,或者主库写入QPS长期超过单机承载,分表无济于事,因为所有表还在同一台机器。此时需要将不同分片放到独立实例,突破单机资源限制。
# 根据 user_id 选择不同数据库实例配置
def get_db_config(user_id):
db_nodes = [
{"host": "192.168.0.1", "port": 3306},
{"host": "192.168.0.2", "port": 3306},
{"host": "192.168.0.3", "port": 3306}
]
idx = user_id % len(db_nodes)
return db_nodes[idx]
# 获取连接配置后访问对应库
config = get_db_config(10023)
print(config["host"])
分库后,跨库 JOIN 和分布式事务会成为新难题,通常要借助 ShardingSphere 等中间件,并在业务上尽量规避跨片操作。
选型前先做瓶颈定位
在动手拆分前,建议先通过监控确认系统卡点:
- 若是慢查询且实例负载低,优先分表
- 若是磁盘容量或写吞吐到顶,必须分库
- 若既需扩资源又要降单表大小,采用分库分表
分库分表不是银弹,拆分后带来的路由、扩容和运维复杂度,需要在架构评审时一并评估。
总结
单表过大时,分表适合解决检索性能问题,分库适合解决资源上限问题。理清真实瓶颈,才能用更小代价换来实现平滑扩容。