分库分表是一个非常核心的数据库架构问题,下面我们来详细拆解一下 MySQL 在什么情况下需要分库分表,以及做出这个决策的具体依据。
核心思想
分库分表本质上是一种 "空间换时间" 和 "分治" 的思想。通过将数据分散到多个数据库或表中,来降低单个数据库实例的负载,从而解决由于数据量过大、访问量过高导致的性能瓶颈问题。
重要前提:分库分表是最后的手段,不是首选方案。 它会带来巨大的复杂性,应优先考虑优化。
一、什么情况下需要考虑分库分表?
主要分为以下三大类场景:
1. 数据量过大(解决"存储"瓶颈)
-
单表数据量过大:当一张表的数据量增长到一定程度时,即使有索引,其 CURD 性能也会显著下降。
- 常见现象 :
COUNT(*)、全表扫描、范围查询等操作变得非常慢。表文件(.ibd)变得巨大,数据维护(如ALTER TABLE)耗时极长,甚至可能锁表很久。
- 常见现象 :
-
数据库总体数据量过大:整个数据库实例的磁盘空间占用接近上限,或者备份/恢复时间变得不可接受。
2. 并发量过高(解决"性能"瓶颈)
-
高并发读写:应用的 QPS(每秒查询数)和 TPS(每秒事务数)非常高,导致数据库连接数占满、CPU 和 IO 负载长期处于高位。
-
锁竞争严重:大量的并发写操作导致行锁、表锁竞争激烈,事务等待时间变长,请求响应时间(RT)增加。
3. 业务发展需要(解决"可用性"和"扩展性"瓶颈)
-
系统可用性要求高:单点数据库一旦宕机,整个系统就不可用。需要通过分库来实现多副本、读写分离,提高系统的容灾能力。
-
系统需要水平扩展:业务快速发展,数据量和访问量预计会呈指数级增长。单一服务器的垂直扩展(升级 CPU、内存、磁盘)成本高昂且有物理上限,需要一种能够水平扩展(加机器)的架构。
二、分库分表的依据(量化指标和原则)
虽然具体数值因硬件、业务逻辑和数据库设计而异,但业界有一些常见的经验阈值作为参考。
1. 数据量依据
-
单表行数 :当单表行数预计将达到或超过 500万到1000万 时,就需要开始考虑分表。这并不是一个绝对的上限,但超过这个数,B+Tree 的深度会增加,查询性能会开始出现可感知的下降。达到数千万甚至上亿时,性能问题会非常明显。
-
单表物理大小 :当单表物理文件大小超过 几十GB(例如 20GB~50GB)时,管理、备份和查询都会变得困难。
2. 并发量依据
-
数据库连接数 :
SHOW PROCESSLIST经常显示连接数接近或达到max_connections的上限,且有大量Sleep状态的连接或慢查询。 -
系统资源 :数据库服务器的 CPU 使用率持续高于 70% ,IO 等待时间(
iowait)过高。 -
QPS/TPS:根据业务能容忍的响应时间,当 QPS 达到数千甚至更高,并且通过缓存、SQL优化等手段已无法有效降低数据库压力时。
3. 业务原则
-
先优化,后拆分:
-
SQL 优化:检查并优化慢查询。
-
索引优化:为查询条件建立合适的索引。
-
引入缓存:使用 Redis 等缓存热点数据,减少对数据库的读压力。
-
读写分离:使用主从复制,将读请求分流到从库。
-
升级硬件:进行垂直扩展(Scale Up)。
-
当以上手段都难以满足需求时,才考虑分库分表。
-
-
选择合适的拆分键(Sharding Key):这是分库分表成功与否的关键。
-
选择依据 :应选择在大多数查询场景中都会用到的字段,例如
user_id、order_id、tenant_id等。 -
要求:数据分布均匀,避免热点;能避免或减少跨分片查询。
-
三、分库分表的具体方式
| 方式 | 描述 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 水平分表 | 将同一张表 的数据按某种规则(如范围、Hash)拆分到同一个数据库的多个结构相同的表中。 | - 解决单表数据量过大问题。 - 对应用透明性相对较好(通过中间件)。 | - 仍在同一个数据库实例,无法解决IO、CPU、连接数瓶颈。 - 单点故障风险仍在。 | 数据量巨大但并发不高的场景。 |
| 垂直分表 | 将一张宽表按字段访问频率或业务模块拆分成多个小表("大表拆小表")。例如,将商品的基本信息和详情描述分开。 | - 避免单行数据过大,提高热点数据的缓存效率。 - 减少IO。 | - 需要联表查询,增加了应用复杂度。 | 表字段非常多,且有明显冷热数据区分。 |
| 垂直分库 | 按业务模块将不同的表拆分到不同的数据库中。例如,将用户库、订单库、商品库分离。 | - 业务清晰,解耦。 - 能将负载分摊到不同数据库服务器。 | - 无法解决单张表过大的问题。 - 跨库事务(分布式事务)复杂。 | 业务模块清晰,耦合度低的系统。 |
| 水平分库 | 将同一张表 的数据按某种规则拆分到多个数据库的多个结构相同的表中。这是最彻底的分库分表。 | - 同时解决数据量和并发量问题。 - 支持水平扩展,系统可用性高。 | - 复杂度最高:分布式事务、跨库JOIN、全局主键ID、路由问题等。 | 海量数据+高并发的终极解决方案。 |
实际应用中,通常是多种方式结合使用,例如先垂直分库(按业务拆分),再对核心大表进行水平分表。
四、分库分表带来的问题与挑战
-
分布式事务:数据分布在不同的数据库,如何保证事务的 ACID 特性?通常需要引入 Seata、TCC、Saga 等分布式事务解决方案,或最终一致性思想。
-
跨库 JOIN:原本简单的联表查询变得极其困难,甚至无法实现。解决方案包括:
-
业务层做多次查询然后组装。
-
使用宽表或冗余字段。
-
使用搜索引擎(如 Elasticsearch)来应对复杂查询。
-
-
全局唯一主键:在多个分片上不能使用数据库自增 ID,需要引入分布式 ID 生成方案,如 Snowflake、UUID、数据库号段等。
-
数据迁移与扩容:如何平滑地从单库单表迁移到分库分表?后期如果需要增加分片数量,如何重新分片并迁移数据?这需要工具(如 ShardingSphere-Scaling)和周密的设计。
-
SQL 限制:很多复杂的 SQL 可能无法支持,例如子查询、函数、排序和分页等,在分片场景下实现成本很高。
总结
决策流程可以概括为:
-
监控预警:持续监控数据库的各项指标(数据量、QPS、连接数、CPU/IO)。
-
优化先行:遇到瓶颈,首先进行 SQL 优化、索引优化、引入缓存、读写分离等。
-
评估依据:当优化后仍无法满足,且数据量/并发量接近或超过上述经验阈值时,启动分库分表方案设计。
-
选择方案:根据业务特点,选择是垂直拆分还是水平拆分,并设计出合理的分片键和分片规则。
-
应对挑战:提前规划好如何解决分布式事务、全局ID、跨库查询等衍生问题。
分库分表是一把双刃剑,它能解决系统的可扩展性问题,但同时也极大地增加了系统的复杂度。因此,务必在充分评估和准备后再实施。现在也有很多成熟的中间件(如 Apache ShardingSphere、MyCat)可以帮助我们降低分库分表的实施和管理成本。