今天是《分库分表 ShardingSphere 原理与实战》系列的开篇,市面上关于分库分表的资料虽然很多,但知识点往往比较零碎,要么是简单堆砌概念,要么是浅尝辄止,很难找到一个系统且深入的系列。这个系列的目标是,从基础理论出发,手把手进行ShardingSphere实战,并结合源码解读和常见问题剖析,力求让新人也能看懂。
话不多说,我们直接进入正题。
在开始ShardingSphere框架的实战之前,我们先来系统性地复习一下分库分表的基础概念。技术名词大多晦涩,理解核心思想远比死记硬背更重要。

什么是分库分表
分库分表是为了应对海量数据场景下,单库、单表数据量过大导致数据库性能持续下降而演变出的技术方案。
它由“分库”和“分表”两个独立概念组成,但因为实际操作中两者常常并行,所以习惯合称为分库分表。

其核心思想是,通过一定的规则,将原本数据量庞大的数据库拆分成多个独立的数据库,将大表拆分成若干小表,从而让单个库、表的性能(尤其是响应速度)达到最优,最终提升整个数据库集群的性能。
为什么需要分库分表?
单机数据库的存储能力和连接数存在上限,很容易成为整个系统的瓶颈。当单表数据量在百万级别时,我们通常还能通过增加从库、优化索引等手段来提升性能。
但是,一旦数据量朝着千万甚至亿级增长,无论怎么优化,很多数据库操作的性能都会急剧下降。为了减轻数据库负担,缩短查询时间,提升响应速度,分库分表就成了必然选择。
为什么需要分库?
- 容量瓶颈:分配给数据库实例的磁盘容量是固定的。数据持续快速增长,单机容量很快会被耗尽,最直接的解决方案就是增加容量,也就是分库。
- 连接数瓶颈:单机的容量可以扩展,但数据库的连接数是有限的。在高并发场景下,多个业务同时访问同一个数据库,很容易耗尽连接数,导致
too many connections 错误,使后续请求无法访问。
可以通过以下命令查看MySQL的最大连接数:
show variables like '%max_connections%'

将单一数据库按业务拆分成订单库、会员库、积分库等,不仅能有效分摊读写压力,也提高了系统的容错性。如果你想了解更多关于数据库设计与高可用架构的实践,可以参考云栈社区的相关讨论。
为什么需要分表?
做过报表或大数据量查询的同学,可能都经历过一条SQL执行几十秒甚至更久的场景。
导致查询慢的原因很多,但“数据量过大”是MySQL自身难以通过优化(如索引)根治的问题。其根本原因在于InnoDB存储引擎的聚簇索引B+树层级会随着数据增长而变高,导致磁盘IO次数增加,查询性能自然下降。
阿里的开发手册中有一条建议:单表行数超过500万行或单表容量超过2GB,才推荐进行分库分表。当然,这只是一个参考值,很多公司单表几千万甚至上亿数据也仍在运行。

什么时候应该考虑分库分表?
技术交流群里经常有人问:到底什么情况下才需要考虑分库分表?
关键在于,分库分表解决的是 “现存海量数据”带来的性能瓶颈,以及对 “未来持续激增”的数据量做出的架构预判。
最核心的指标就是数据量。 以一个资源网站为例,初期每日新增数据很少,单库单表足以应对。但当某天数据量暴增至每日百万级,资源表总数据达到千万级,查询响应明显变慢,性能瓶颈开始显现。
对于MySQL而言,当单表数据达到亿级,传统优化手段(加索引、SQL调优)收效甚微时,就应当严肃考虑分库分表了。
既然MySQL存海量数据有瓶颈,那能不能直接用其他方案替代?比如高性能的MongoDB?
当然可以,但这取决于数据类型。目前互联网公司的核心业务数据(如交易、账户)大多仍存储在MySQL、Oracle这类关系型数据库中,因为它们具备NoSQL难以比拟的ACID事务特性、稳定性和成熟的生态。而像评论、点赞这类非核心数据,则完全可以考虑使用MongoDB。
如何分库分表?
分库分表的核心在于对数据进行 分片,并将数据相对均匀地路由到不同的库和表中,同时能够快速定位分片数据并整合查询结果。
拆分可以从两个维度进行:垂直(纵向)和水平(横向)。下面我们以经典的订单业务为例进行说明。

垂直拆分
1. 垂直分库
垂直分库通常按照业务和功能维度拆分,遵循“专库专用”原则。例如,将订单、支付、会员、积分等相关表分别放入独立的订单库、支付库、会员库、积分库。

不同业务间禁止直接跨库连表查询,数据交互应通过API接口进行,这也是微服务拆分的重要依据之一。垂直分库分摊了单库压力,但并未解决单表数据量过大的根本问题。
2. 垂直分表
垂直分表针对字段众多的大表,将表中一些不常用或长度较大的字段拆分出去,形成一种“大表拆小表”的模式。
例如,一张t_order表有几十个字段,其中订单金额字段计算频繁。为了不影响主表性能,可以将金额相关字段拆分到独立的t_order_price_expansion扩展表中,两张表通过order_no关联。

数据库以行为单位加载数据到内存。拆分后,核心表只保留高频访问的短字段,单次能加载更多数据到内存,减少磁盘IO,提高索引命中率,从而提升性能。
水平拆分
垂直拆分后,单库/表数据量过大的问题可能依然存在。此时就需要进行水平拆分。
1. 水平分库
将同一张表按规则拆分到不同的数据库实例中,每个库可以位于不同服务器,实现水平扩展。

例如,db_order_1和db_order_2两个库都有结构相同的t_order表。访问订单时,通过对订单号取模(订单编号 mod 2)来决定操作哪个库。这种方式解决了单库存储和性能瓶颈,但也引入了数据访问需要路由的复杂度。
2. 水平分表
在同一个数据库内,将一张大数据量表按规则切分成多个结构完全相同的子表,每个子表只存储原表的一部分数据。
例如,一张900万数据的t_order表,水平拆分成t_order_1、t_order_2、t_order_3三张表,每张存300万数据。

但水平分表后,所有子表仍在同一个数据库实例中,会竞争同一台物理机的CPU、内存、网络IO等资源。要进一步提升,就需要将子表分散到不同的数据库实例,即结合水平分库,实现分布式存储。

数据该存在哪个库表?
分库分表后,一张逻辑表会分布到多个物理库和表中,那么数据到底该存到哪里?
这取决于我们反复提到的 “一定规则”,即分片路由算法。常见的算法有:
1. 取模算法
这是最常见的路由方式。对某个关键字段(如订单号order_no)进行哈希取模:hash(order_no) mod N(N为数据库或子表数量)。根据余数i决定数据路由到第i个库或表。

- 优点:实现简单,数据分布相对均匀。
- 缺点:对集群扩缩容不友好。当机器数N变化时(如宕机一台,N变为N-1),取模规则改变,会导致大量数据路由错误,需要迁移数据。
2. 范围限定算法
根据某些范围字段,如时间或ID区间进行拆分。例如,将user_id在1~1000万的数据放入t_user_1,1000~2000万放入t_user_2,以此类推。

- 优点:单表数据量可控;水平扩展简单,增加节点无需迁移历史数据。
- 缺点:容易产生“数据热点”。例如按时间分片,大促期间的数据会集中写入最新分片,造成该分片负载过高。
3. 范围 + 取模算法
结合上述两种算法,先按范围分库,再在库内按取模分表。例如,先定义每个库存1000万用户数据,db_1存userId 1~1000万,db_2存1000~2000万。然后在每个库内,将用户表拆分成t_user_1、t_user_2等,再对userId取模路由。

这既避免了数据倾斜,又使得水平扩展(加库)变得容易,无需迁移数据。
4. 预定义算法 & 地理位置分片
- 预定义算法:事先明确分片规则,直接将某类数据路由到指定库表。
- 地理位置分片:按地域划分,如华东、华北数据存入不同分片。
分库分表带来的挑战
分库分表在提升性能的同时,也极大地增加了系统架构的复杂度,引入了一系列新问题:
-
跨节点查询问题:分页、排序、联合查询(JOIN)变得异常复杂。需要在多个节点查询数据,然后在应用层进行合并、排序,效率低下。

-
分布式事务一致性:数据分布在不同库,跨库事务成为难题。后续通常需要引入Seata或基于XA协议等方案来解决。
-
全局唯一主键:分表后,数据库自增ID无法保证全局唯一。需要引入独立的分布式ID生成器(发号器)。
-
多库多表治理:面对成百上千个分片表,如何高效地进行建表、监控、排查问题,是一个巨大的运维挑战。
-
历史数据迁移:架构落地后,如何平滑地将历史数据迁移到新的分片集群,并保证迁移过程中数据的完整性和业务连续性,是一个复杂工程。
分库分表的架构模式
主要分为两种模式:Client模式(客户端模式)和Proxy模式(代理模式)。
Client模式
分库分表的逻辑整合在应用内部。应用直接连接多个数据库,执行拆分后的SQL,并在本地进行数据聚合。

Proxy模式
在应用与数据库之间部署一个独立的代理服务。对应用来说,代理服务就是数据库。代理服务实现了MySQL协议,根据内部配置将SQL转发到正确的分片执行。

如何选择?
| 方面 |
Client模式 |
Proxy模式 |
| 性能 |
直接连接DB,链路短,性能稍好。 |
多一层网络跳转,略有损耗。 |
| 复杂度 |
以JAR包形式集成,使用简单。 |
需独立部署和维护高可用代理服务。 |
| 升级 |
升级需所有应用更新JAR包,成本高。 |
升级代理服务即可,业务无感知。 |
| 治理监控 |
治理功能内嵌,不便统一管控。 |
便于实现统一的SQL限流、监控、审计等。 |
本文系统回顾了分库分表的核心概念,为后续深入ShardingSphere实战打下基础。下一篇文章,我们将对ShardingSphere的基础知识点进行全面梳理。
