分库分表详解

分库分表是什么

分库分表:把原来一张大表的数据,拆分到多个库、多张表,分摊存储与查询压力。

先判断:是否真的需要拆分

在动手之前,有一个务实的判断基准:单表数据量在 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. 跨分片查询问题

  • 单分片查询:带分片键,路由到单表,性能最好,业务优先保证这种场景

  • 多分片查询:不带分片键,需要全分片扫描(广播查询),性能差

优化方案:

  1. 强制查询携带分片键;

  2. 冗余字段;

  3. 搭建独立搜索引擎 ES 做复杂检索,分库分表只负责基础 CRUD;

  4. 宽表设计,减少 join

3. 分布式事务

分库后跨库操作,本地事务失效 。方案对比:

  1. TCC:侵入业务代码,性能好,开发成本高

  2. SAGA:长事务,适合长流程,补偿逻辑复杂

  3. Seata AT 模式(常用):无侵入,undo_log,适合大部分业务,存在锁和性能损耗

  4. 最终一致性 + 业务补偿:很多互联网业务不追求强一致,采用此方案(对账 + 补偿)

4. 数据倾斜

某个分片数据远多于其他分片,压力集中。 原因:分片键分布不均、热点用户。 解决:更换分片键、增加虚拟分片、冷热分离。

5. 分页、排序、聚合函数

order by / limit:跨分片分页,不能直接取,要每个分片查询后,应用内存合并再分页,大 offset 性能极差。 优化:

  • 避免深分页;

  • 利用分片键做时间 ID 分页;

  • 复杂统计交给 ES。

6. 广播表、绑定表

  • 广播表:字典表、配置表,所有分片库都存一份,关联查询不用跨库

  • 绑定表:主表和子表使用同一个分片键,保证主表和子表数据落在同一个分片,支持本地 join(比如 order 订单表、order_item 订单明细表,分片键都是 user_id)

分库分表的常见误区

|-----------------|-----------------------------|
| 误区 | 正确理解 |
| 分表后查询一定更快 | 如果查询需要扫描所有分片,反而可能更慢 |
| 分库就能自动提高可用性 | 仍需要副本、故障切换、备份和恢复机制 |
| 全局 ID 就能保证业务不重复 | 唯一 ID 不等于幂等,业务仍可能重复执行 |
| 加了分片中间件就不用设计分片键 | 分片键仍决定路由效率和数据分布 |
| 所有业务表都应该一起分片 | 应结合数据规模、访问模式和关联关系决定 |
| 数据量超过某个数字就必须分表 | 没有适用于所有业务的固定阈值,应以实际瓶颈为依据 |
| 分库分表能解决所有高并发问题 | 连接池、缓存、业务热点、锁竞争和下游依赖仍可能成为瓶颈 |

相关推荐
数据库小学妹1 小时前
MySQL表碎片与 OPTIMIZE TABLE:空间回收的代价与边界
数据库·后端·mysql
时间售卖铺1 小时前
MySQL 表的约束
运维·数据库·mysql·云原生
tongyiixiaohuang2 小时前
物料成本核算单同步返回信息策略实战教程:从金蝶云星空回写 MySQL 的闭环设计
数据库·mysql
深度学习04072 小时前
MySQL InnoDB Cluster 部署完整文档
android·mysql·adb
时间售卖铺3 小时前
MySQL 安装部署
运维·数据库·mysql·云原生
惊讶的猫5 小时前
mysql-多表连接查询(JOIN)
数据库·sql·mysql
小羊没烦恼!5 小时前
TypedSql:在 C# 类型系统上实现一个 SQL 查询引擎
java·linux·运维·服务器·开发语言·数据库·mysql
xiaogai_gai5 小时前
委外出入库单从旺店通同步到 MySQL:基于轻易云的实战方案
数据库·mysql
Mortalbreeze5 小时前
MySQL 基础篇(十):用户管理
数据库·mysql