分库分表是什么
分库分表:把原来一张大表的数据,拆分到多个库、多张表,分摊存储与查询压力。
先判断:是否真的需要拆分
在动手之前,有一个务实的判断基准:单表数据量在 500 万行以下时,优先通过索引优化、SQL 改写、缓存或读写分离来解决,不必急于分片。当单表行数突破 1000 万,InnoDB 的 B+ 树层级可能从 3 层增至 4 层,查询会多一次磁盘 IO,性能衰减开始明显;突破 5000 万后,多数 SQL 优化手段的边际收益急剧下降,此时水平拆分才成为必要选项。分库分表解决的是单机容量、连接数、IO 带宽的硬上限,而不是所有性能问题的通用解药
垂直拆分(按列拆)
垂直分表:一张大表,拆成多张表,库不变,按字段拆分 ,通常保留相同的主键
例子:用户表user(id,name,password,phone,address,log_time) 拆:user_base(id,name,password)、user_ext(id,phone,address,log_time) 特点:
-
冷热字段分离;大文本、长字段单独放一张表
-
减少单表行宽度,减少 IO,查询不需要加载大量无用字段
-
缺点:关联查询需要 join 多张表
垂直分库:按业务模块拆,不同业务放不同数据库
例子:一个库拆成 order_db、user_db、iot_device_db 特点:
-
业务隔离,互不影响;每个库独立实例,资源隔离
-
跨库无法直接 join,需要应用层做关联
-
分布式事务问题
水平拆分(按行拆,最常用)
水平分表:库不变,一张表按规则拆成多张表,结构完全一样,数据行分散
例:order_0、order_1...order_15,同一个库内 16 张订单表
水平分库分表:既分库又分表,跨多个数据库实例,每张库内再分多张表。
例:db0.order_0~order_7,db1.order_0~order_7
水平拆分的核心:分片键与分片算法
分片策略的成败,首先取决于分片键的选择。分片键必须是高频查询条件中携带的字段,否则大量查询会退化为广播到所有分片。订单表按 user_id 分片,用户维度的查询可以精准路由,但按 order_id 查询就需要扫描所有分片;如果两种查询并存,可以采用基因法,将 user_id 的一部分信息编码进 order_id,使通过 order_id 也能直接计算出分片位置。
分片算法主要有几类,各自的权衡不同:
|-----------|------------------------|---------------------------|
| 算法 | 优势 | 代价与适用场景 |
| 范围分片 | 范围查询高效,天然支持按时间归档与扩容 | 容易产生热点(如按时间递增写入),需要配合分裂策略 |
| 哈希取模 | 数据分布均匀,实现简单 | 扩容时几乎需要全量重分布,扩容成本高 |
| 一致性哈希 | 扩容时迁移量可控,适合增长快、不能停服的业务 | 实现较复杂,通常依赖中间件或自行封装 |
| 枚举/复合 | 可按业务枚举值或组合维度定制 | 通用性弱,需针对具体场景设计 |
分片键选择原则:优先选择查询最多、尽量避免跨分片的字段,如 user_id、order_id;不要选更新频繁、查询分散字段。
中间件选型:ShardingSphere 与 MyCat
分库分表的落地依赖中间件,当前主流选择集中在 Apache ShardingSphere 和 MyCat 之间,核心差异在于部署形态。
ShardingSphere 提供两种形态:Sharding-JDBC 以 JAR 包嵌入应用,无额外网络跳转,性能损耗极小,适合 Java 技术栈和对延迟敏感的核心链路;Sharding-Proxy 以独立代理部署,对应用语言透明,Go、Python 等非 Java 应用也可通过标准 MySQL 协议接入。ShardingSphere 社区活跃,功能迭代快,对复杂 SQL 的兼容性更好,是新项目的优先选择。
MyCat 是服务端代理模式,配置相对简单、上手快,适合存量系统快速验证或过渡。但其社区活跃度已明显下降,复杂 SQL 兼容性和新特性支持不如 ShardingSphere,且代理层会引入额外的网络与解析开销。中小团队或新项目通常首选 Sharding-JDBC,多语言环境则考虑 Sharding-Proxy。
核心难点
1. 分布式主键
分表后不能依赖数据库自增 ID(多个表会 ID 重复) 方案:
-
Snowflake 雪花算法(最常用):时间戳 + 机器 ID + 序列号,生成全局唯一 ID。雪花缺点:依赖系统时间,时钟回拨会产生重复 ID,需要做时钟校验
-
Redis 自增
-
数据库号段模式
2. 跨分片查询问题
-
单分片查询:带分片键,路由到单表,性能最好,业务优先保证这种场景
-
多分片查询:不带分片键,需要全分片扫描(广播查询),性能差
优化方案:
-
强制查询携带分片键;
-
冗余字段;
-
搭建独立搜索引擎 ES 做复杂检索,分库分表只负责基础 CRUD;
-
宽表设计,减少 join
3. 分布式事务
分库后跨库操作,本地事务失效 。方案对比:
-
TCC:侵入业务代码,性能好,开发成本高
-
SAGA:长事务,适合长流程,补偿逻辑复杂
-
Seata AT 模式(常用):无侵入,undo_log,适合大部分业务,存在锁和性能损耗
-
最终一致性 + 业务补偿:很多互联网业务不追求强一致,采用此方案(对账 + 补偿)
4. 数据倾斜
某个分片数据远多于其他分片,压力集中。 原因:分片键分布不均、热点用户。 解决:更换分片键、增加虚拟分片、冷热分离。
5. 分页、排序、聚合函数
order by / limit:跨分片分页,不能直接取,要每个分片查询后,应用内存合并再分页,大 offset 性能极差。 优化:
-
避免深分页;
-
利用分片键做时间 ID 分页;
-
复杂统计交给 ES。
6. 广播表、绑定表
-
广播表:字典表、配置表,所有分片库都存一份,关联查询不用跨库
-
绑定表:主表和子表使用同一个分片键,保证主表和子表数据落在同一个分片,支持本地 join(比如 order 订单表、order_item 订单明细表,分片键都是 user_id)
分库分表的常见误区
|-----------------|-----------------------------|
| 误区 | 正确理解 |
| 分表后查询一定更快 | 如果查询需要扫描所有分片,反而可能更慢 |
| 分库就能自动提高可用性 | 仍需要副本、故障切换、备份和恢复机制 |
| 全局 ID 就能保证业务不重复 | 唯一 ID 不等于幂等,业务仍可能重复执行 |
| 加了分片中间件就不用设计分片键 | 分片键仍决定路由效率和数据分布 |
| 所有业务表都应该一起分片 | 应结合数据规模、访问模式和关联关系决定 |
| 数据量超过某个数字就必须分表 | 没有适用于所有业务的固定阈值,应以实际瓶颈为依据 |
| 分库分表能解决所有高并发问题 | 连接池、缓存、业务热点、锁竞争和下游依赖仍可能成为瓶颈 |