当单库单表的数据量持续增长(千万、上亿),或并发写入远超单库上限时,就需要分库分表。本文系统讲解拆分策略、分片键选择、全局 ID、扩容与跨库 Join/事务,以及主流的 ShardingSphere 中间件实践。
单库单表的容量与性能瓶颈:
分库分表的目的:通过水平拆分把数据分布到多库多表,降低单表数据量、分散读写压力,实现水平扩展。
垂直分库: 订单库(订单表) | 用户库(用户表) | 商品库(商品表)
垂直分表: user(常用字段) + user_ext(大字段: bio/avatar)水平分表:把同一张表的数据按某规则分到多张结构相同的表。 水平分库:把数据分布到多个数据库实例。
水平分表: order_0, order_1, order_2, order_3(按 order_id 取模)
水平分库: db_order_0, db_order_1(每库内再分表)| 算法 | 说明 | 示例 | 特点 |
|---|---|---|---|
| 取模 | hash(key) % 表数 | user_id % 4 | 简单,但扩容需重分片 |
| 范围 | 按 ID/时间区间 | id < 1000 -> t0 | 扩容友好,但可能数据倾斜 |
| 一致性哈希 | 环形哈希,减少迁移 | 均匀分布 + 虚拟节点 | 扩容只需迁移少量数据 |
| 哈希分片 | 对分片键做哈希 | md5(user_id) % N | 分布均匀 |
取模 vs 范围的选择:
分片键是分库分表的核心决策,选错会导致大部分查询变成"全分片扫描"。
选择原则:
user_id、order_id)应作为分片键。常见问题——非分片键查询怎么办:
order_id 查但按 user_id 分片,可用 映射表(维护 order_id→user_id 关系)或 冗余字段(在表里冗余 user_id)。分库分表后,数据库自增主键失效,需要全局唯一 ID。常用方案(详见「分布式 ID 生成方案」):
分片键若用全局 ID,建议ID 末尾带分片信息(如 snowflake 的机器位/或 ID 后拼分片号),便于按 ID 定位分片,避免每次都 rehash。
分库分表最大的代价是牺牲了关系型数据库的关联与事务能力:
跨库写操作无法用单库事务,需分布式事务方案(详见「分布式事务与最终一致性」):
取模分片的扩容难题:从 N 个分片扩到 M 个,hash(key) % N 变成 % M,几乎所有数据都要迁移。
解决思路:
% N 的,扩容到 % 2N 后,只有一半数据需要迁移(hash % 2N 结果为奇数序号的),且迁移逻辑简单(旧分片的一半)。ShardingSphere 是 Apache 顶级开源项目,核心产品:
| 产品 | 定位 | 说明 |
|---|---|---|
| ShardingSphere-JDBC | JDBC 增强(客户端) | 应用内代理,改连接即可,无需独立部署,主流 |
| ShardingSphere-Proxy | 独立代理(服务端) | 模拟数据库,客户端无需改动 |
| ShardingSphere-Sidecar | 云原生 | 用于 Kubernetes 场景 |
# application.yml
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
url: jdbc:mysql://localhost:3306/order_db_0
ds1:
url: jdbc:mysql://localhost:3306/order_db_1
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds${0..1}.t_order_${0..3}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: order_inline
key-generate-strategy:
column: order_id
key-generator-name: snowflake
sharding-algorithms:
order_inline:
type: INLINE
props:
algorithm-expression: t_order_${order_id % 4}
key-generators:
snowflake:
type: SNOWFLAKE// 使用上与普通 Mapper 完全一致,ShardingSphere 自动路由分片
orderMapper.insert(order); // 自动生成雪花ID并按 order_id 路由
Order o = orderMapper.selectById(id); // 自动路由到正确分片ShardingSphere-JDBC 的核心能力:
LIMIT 跨分片的深度分页需注意。LIMIT 100000,10)在分片下会全分片取出再归并,性能差,需用游标/分页键优化。不要过早分库分表。它带来的问题:
建议:优先用读写分离、缓存、索引优化、归档冷数据解决;当单表数据量到千万级且持续增长、写入并发成为瓶颈时,再考虑分库分表,并做好分片键设计与扩容预案。
| 特性 | 旧版(本文编写时,MySQL 5.7) | 当前(MySQL 8.0/8.4 LTS) |
|---|---|---|
| 默认字符集 | utf8(需显式配置 utf8mb4) | utf8mb4(MySQL 8.0 起默认) |
| 索引 | 普通 B+Tree | 降序索引、隐藏索引、函数索引(8.0+) |
| SQL 能力 | 常规查询 | 递归 CTE、窗口函数(8.0+) |
| 版本策略 | 5.7 | 8.0(主流)/ 8.4 LTS / 9.x(创新版) |
| Java 驱动 | mysql-connector-java 5.x/8.0 | mysql-connector-j 8.x/9.x |
本文基于 MySQL 5.7 编写,核心概念(索引、事务、锁、MVCC、InnoDB)在 8.0/8.4 中依然适用;8.0 的默认字符集、隐藏索引与 SQL 增强(CTE/窗口函数)是升级后的主要差异。