MySQL 分库分表中间件对比:ShardingSphere vs MyCat vs Vitess
MySQL 分库分表中间件对比:ShardingSphere vs MyCat vs Vitess
当单表数据突破千万级,单库扛不住读写并发,分库分表就成了必选项。但分完之后,跨库 JOIN 怎么办?分布式事务怎么搞?选哪个中间件?本文从架构原理、分片策略、跨库查询、分布式事务到迁移方案,全面对比三大主流中间件。
一、为什么需要分库分表
1.1 单库瓶颈
┌────────────────────────────────────────────────┐
│ 单库 MySQL 性能瓶颈 │
├────────────────────────────────────────────────┤
│ │
│ 数据量维度: │
│ ┌──────────┬──────────────────────────────┐ │
│ │ < 500万 │ 性能良好,正常索引即可 │ │
│ │ 500-1000万│ 需要优化索引,查询变慢 │ │
│ │ 1000-5000万│ 必须深度优化,分表考虑 │ │
│ │ > 5000万 │ 强烈建议分库分表 │ │
│ └──────────┴──────────────────────────────┘ │
│ │
│ 并发维度: │
│ ┌──────────┬──────────────────────────────┐ │
│ │ QPS < 1000│ 单机可承载 │ │
│ │ QPS 1000-5000│ 读写分离 + 缓存 │ │
│ │ QPS > 5000│ 需要分库分散压力 │ │
│ └──────────┴──────────────────────────────┘ │
│ │
│ 瓶颈点: │
│ 1. 磁盘 I/O — 数据量大时随机读写变慢 │
│ 2. 内存 — buffer pool 命中率下降 │
│ 3. 锁竞争 — 大表 DDL 耗时极长 │
│ 4. 复制延迟 — binlog 太大,从库追不上 │
│ 5. 备份恢复 — 单库备份耗时长 │
│ │
└────────────────────────────────────────────────┘1.2 分库分表概念
┌──────────────────────────────────────────────────────┐
│ 分库 vs 分表 │
├──────────────────────────────────────────────────────┤
│ │
│ 【分表】同一个数据库内,拆成多张表 │
│ ┌────────┐ ┌────────┐┌────────┐┌────────┐ │
│ │ orders │ → │orders_0││orders_1││orders_2│ │
│ │ 5000万 │ │ 1667万 ││ 1667万 ││ 1667万 │ │
│ └────────┘ └────────┘└────────┘└────────┘ │
│ 解决:单表数据量过大 │
│ 不解决:单库并发压力 │
│ │
│ 【分库】不同数据库实例,分散到多台机器 │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ DB_0 │ │ DB_0 │ │ DB_1 │ │
│ │ orders │ → │orders_0│ │orders_1│ │
│ │ 5000万 │ │ 2500万 │ │ 2500万 │ │
│ └────────┘ └────────┘ └────────┘ │
│ 解决:数据量 + 并发压力 │
│ │
│ 【分库分表】两者结合 │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ │
│ │ DB_0 │ │ DB_0 │ │ DB_1 │ │
│ │ orders │ → │orders_00 │ │orders_10 │ │
│ │ 1亿 │ │orders_01 │ │orders_11 │ │
│ │ │ │ 2500万 │ │ 2500万 │ │
│ └────────┘ └──────────┘ └──────────┘ │
│ 2库 × 2表 = 4个分片 │
│ │
└──────────────────────────────────────────────────────┘二、分片策略
2.1 范围分片(Range)
┌────────────────────────────────────────────┐
│ 范围分片示例 │
├────────────────────────────────────────────┤
│ │
│ 分片键:user_id │
│ │
│ ┌─────────────┬─────────────┐ │
│ │ 0 - 999999 │ → shard_0 │ │
│ │ 1000000-1999999│ → shard_1 │ │
│ │ 2000000-2999999│ → shard_2 │ │
│ │ 3000000+ │ → shard_3 │ │
│ └─────────────┴─────────────┘ │
│ │
│ 优点: │
│ - 范围查询友好(WHERE id BETWEEN ...) │
│ - 扩容简单(新增分片接收新数据) │
│ │
│ 缺点: │
│ - 热点问题(新用户都在最新分片) │
│ - 数据分布不均匀 │
│ │
└────────────────────────────────────────────┘2.2 Hash 分片
┌────────────────────────────────────────────┐
│ Hash 分片示例 │
├────────────────────────────────────────────┤
│ │
│ 分片键:user_id │
│ 分片数:4 │
│ │
│ shard = user_id % 4 │
│ │
│ ┌──────────┬──────────┐ │
│ │ user_id │ shard │ │
│ ├──────────┼──────────┤ │
│ │ 1001 │ shard_1 │ │
│ │ 1002 │ shard_2 │ │
│ │ 1003 │ shard_3 │ │
│ │ 1004 │ shard_0 │ │
│ └──────────┴──────────┘ │
│ │
│ 优点: │
│ - 数据分布均匀 │
│ - 无热点问题 │
│ │
│ 缺点: │
│ - 扩容困难(需要 rehash,数据迁移量大) │
│ - 范围查询退化为全分片扫描 │
│ │
└────────────────────────────────────────────┘2.3 一致性 Hash(Consistent Hashing)
┌──────────────────────────────────────────────────┐
│ 一致性 Hash 环 │
├──────────────────────────────────────────────────┤
│ │
│ 0 │
│ │ │
│ shard_C ────── shard_A │
│ │ │ │
│ │ │ │
│ shard_D shard_B │
│ │ │
│ ∞ │
│ │
│ Hash 空间是一个环: │
│ - 节点(shard)和数据都映射到环上 │
│ - 数据顺时针找到最近的节点 │
│ │
│ 扩容时(新增 shard_E): │
│ ┌─────────────────────────────────────┐ │
│ │ 只有 shard_E 和前一个节点之间的数据 │ │
│ │ 需要迁移,其余节点不受影响 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 优点: │
│ - 扩容/缩容时只影响相邻节点 │
│ - 数据迁移量最小 │
│ │
│ 缺点: │
│ - 实现复杂 │
│ - 可能出现数据倾斜(需虚拟节点均衡) │
│ │
└──────────────────────────────────────────────────┘分片策略对比
| 策略 | 数据均匀 | 范围查询 | 扩容难度 | 适用场景 |
|---|---|---|---|---|
| 范围分片 | ❌ 差 | ✅ 好 | ✅ 简单 | 按时间/ID 范围查询多 |
| Hash 分片 | ✅ 好 | ❌ 差 | ❌ 困难 | 等值查询为主 |
| 一致性 Hash | ✅ 好 | ❌ 差 | ⚠️ 中等 | 频繁扩缩容场景 |
三、ShardingSphere
3.1 架构概述
Apache ShardingSphere(原当当 Sharding-JDBC)是目前最活跃的国产分库分表中间件,提供两种部署模式:
┌──────────────────────────────────────────────────────┐
│ ShardingSphere 两种模式 │
├──────────────────────────────────────────────────────┤
│ │
│ 【JDBC 模式】(Client 端) │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ ShardingSphere-JDBC │ │ │
│ │ │ (JAR 包,进程内) │ │ │
│ │ │ - SQL 解析 │ │ │
│ │ │ - 路由 │ │ │
│ │ │ - 改写 │ │ │
│ │ │ - 执行 │ │ │
│ │ │ - 归并 │ │ │
│ │ └──────────┬─────────────────┘ │ │
│ └─────────────┼────────────────────────┘ │
│ ┌──────┼──────┐ │
│ ▼ ▼ ▼ │
│ ┌─────┐┌─────┐┌─────┐ │
│ │ DB_0││ DB_1││ DB_2│ (直连数据库) │
│ └─────┘└─────┘└─────┘ │
│ │
│ 优点:无中间层,延迟低,无需额外部署 │
│ 缺点:每个应用实例都需配置,语言绑定 Java │
│ │
│ 【Proxy 模式】(Server 端) │
│ │
│ ┌─────────────┐ │
│ │ 应用层 │ │
│ └──────┬──────┘ │
│ │ MySQL 协议 │
│ ┌──────▼──────────────┐ │
│ │ ShardingSphere-Proxy│ │
│ │ (独立部署) │ │
│ │ - SQL 解析 │ │
│ │ - 路由 │ │
│ │ - 改写 │ │
│ │ - 执行 │ │
│ │ - 归并 │ │
│ └──────┬──────────────┘ │
│ ┌────┼────┐ │
│ ▼ ▼ ▼ │
│ ┌─────┐┌─────┐┌─────┐ │
│ │ DB_0││ DB_1││ DB_2│ │
│ └─────┘└─────┘└─────┘ │
│ │
│ 优点:多语言支持,应用无感知 │
│ 缺点:多一层网络转发,有额外延迟 │
│ │
└──────────────────────────────────────────────────────┘3.2 核心执行流程
┌──────────────────────────────────────────────────────────────┐
│ ShardingSphere SQL 执行流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 原始 SQL: │
│ SELECT * FROM t_order WHERE user_id = 1001 │
│ │
│ ┌─→ 1. SQL 解析 (Parsing) │
│ │ 将 SQL 解析为 AST(抽象语法树) │
│ │ 提取表名、条件、排序、分组等 │
│ │ │
│ ├─→ 2. 路由 (Routing) │
│ │ 根据分片键和分片策略,确定目标分片 │
│ │ user_id=1001, 1001%4=1 → shard_1 │
│ │ │
│ ├─→ 3. SQL 改写 (Rewriting) │
│ │ 将逻辑表名替换为物理表名 │
│ │ SELECT * FROM t_order_1 WHERE user_id = 1001 │
│ │ │
│ ├─→ 4. 执行 (Executing) │
│ │ 并行发送到目标分片执行 │
│ │ 如果是跨分片查询,并行执行后归并 │
│ │ │
│ ├─→ 5. 归并 (Merging) │
│ │ 合并各分片结果集 │
│ │ - 排序归并(ORDER BY) │
│ │ - 分组归并(GROUP BY) │
│ │ - 聚合归并(COUNT/SUM/AVG) │
│ │ │
│ └─→ 返回结果集给应用 │
│ │
└──────────────────────────────────────────────────────────────┘3.3 配置示例(YAML)
# sharding-config.yaml
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
props:
jdbcUrl: jdbc:mysql://192.168.1.101:3306/db_0
username: root
password: Root@123
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
props:
jdbcUrl: jdbc:mysql://192.168.1.102:3306/db_1
username: root
password: Root@123
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: ds_${0..1}.t_order_${0..3}
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: db_mod
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: table_mod
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
shardingAlgorithms:
db_mod:
type: MOD
props:
sharding-count: 2
table_mod:
type: MOD
props:
sharding-count: 4
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
bindingTables:
- t_order,t_order_item
broadcastTables:
- t_config// Java 代码使用
// JDBC 模式:创建数据源
DataSource dataSource = YamlShardingSphereDataSourceFactory.createDataSource(
new File("sharding-config.yaml")
);
// 像操作单表一样操作分片表
String sql = "SELECT * FROM t_order WHERE user_id = ? AND status = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, 1001L);
ps.setInt(2, 1);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getLong("order_id") + ": " + rs.getBigDecimal("amount"));
}
}3.4 ShardingSphere 特性总结
| 特性 | 支持情况 |
|---|---|
| 分库分表 | ✅ 完整支持 |
| 读写分离 | ✅ 内置支持 |
| 分布式事务 | ✅ XA / BASE / LOCAL |
| 数据加密 | ✅ 透明加密 |
| 影子库 | ✅ 压测支持 |
| SQL 方言 | MySQL / PostgreSQL / Oracle |
| 跨库 JOIN | ⚠️ 绑定表支持,非绑定表有限 |
| 子查询 | ⚠️ 部分支持 |
| 多语言 | Proxy 模式支持任意语言 |
四、MyCat
4.1 架构概述
MyCat 是基于阿里 Cobar 演进的国产数据库中间件,曾风靡一时,但目前活跃度下降。
┌──────────────────────────────────────────────────────┐
│ MyCat 架构 │
├──────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 应用层 │ (MySQL 协议连接) │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────────────────────┐ │
│ │ MyCat Server │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ SQL 解析层 │ │ │
│ │ │ (Druid Parser) │ │ │
│ │ └───────────┬───────────┘ │ │
│ │ ┌───────────▼───────────┐ │ │
│ │ │ 路由层 │ │ │
│ │ │ (分片规则匹配) │ │ │
│ │ └───────────┬───────────┘ │ │
│ │ ┌───────────▼───────────┐ │ │
│ │ │ 执行层 │ │ │
│ │ │ (NIO 并发执行) │ │ │
│ │ └───────────┬───────────┘ │ │
│ │ ┌───────────▼───────────┐ │ │
│ │ │ 归并层 │ │ │
│ │ │ (结果集合并) │ │ │
│ │ └───────────────────────┘ │ │
│ └──────┬──────────────────────┘ │
│ ┌────┼────┐ │
│ ▼ ▼ ▼ │
│ ┌─────┐┌─────┐┌─────┐ │
│ │ DN1 ││ DN2 ││ DN3 │ (DataNode) │
│ └─────┘└─────┘└─────┘ │
│ │
│ 特点:Proxy 模式,应用无感知 │
│ 类似 MySQL Server,支持多语言客户端 │
│ │
└──────────────────────────────────────────────────────┘4.2 配置示例
<!-- schema.xml -->
<schema name="TESTDB">
<table name="t_order" primaryKey="id" dataNode="dn1,dn2,dn3,dn4"
rule="mod-long">
<!-- ER 分片:子表跟随父表 -->
<childTable name="t_order_item" primaryKey="id" joinKey="order_id"
parentKey="id"/>
</table>
<!-- 广播表:每个节点都有完整数据 -->
<table name="t_config" type="global" dataNode="dn1,dn2,dn3,dn4"/>
</schema>
<dataNode name="dn1" dataHost="host1" database="db_1"/>
<dataNode name="dn2" dataHost="host1" database="db_2"/>
<dataNode name="dn3" dataHost="host2" database="db_1"/>
<dataNode name="dn4" dataHost="host2" database="db_2"/>
<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select 1</heartbeat>
<writeHost host="hostM1" url="192.168.1.101:3306" user="root"
password="Root@123">
<readHost host="hostS1" url="192.168.1.103:3306" user="root"
password="Root@123"/>
</writeHost>
</dataHost><!-- rule.xml -->
<tableRule name="mod-long">
<rule>
<columns>user_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">4</property>
</function>4.3 MyCat 的局限性
| 局限 | 说明 |
|---|---|
| 维护停滞 | 社区活跃度大幅下降,核心团队转做 MyCat2 |
| MyCat2 不兼容 | MyCat2 完全重写,与 1.x 不兼容 |
| SQL 兼容性差 | 复杂 SQL(子查询、存储过程)支持有限 |
| 聚合函数 | 部分聚合函数(如 DISTINCT + COUNT)结果不正确 |
| 事务支持 | 仅支持单库事务,跨库事务弱(XA 支持不稳定) |
| 连接管理 | 长连接稳定性一般,连接泄漏风险 |
| 监控 | 监控能力弱,需自行扩展 |
⚠️ 避坑:新项目不建议选 MyCat。如果团队已经在用 MyCat 1.x,建议评估迁移到 ShardingSphere。
五、Vitess
5.1 架构概述
Vitess 是 YouTube 开发的 MySQL 数据库集群管理工具,现为 CNCF 毕业项目。它天生为云原生设计,与 Kubernetes 深度集成。
┌──────────────────────────────────────────────────────────────┐
│ Vitess 架构 (云原生) │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 应用层 │ (gRPC / MySQL 协议) │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────────────────────┐ │
│ │ VTGate (查询网关) │ │
│ │ - SQL 解析与路由 │ │
│ │ - 查询规划 │ │
│ │ - 负载均衡 │ │
│ └──────┬──────────────────────┘ │
│ │ VStreamer (复制流) │
│ ┌────┼────┬────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────┐┌─────┐┌─────┐┌─────┐ │
│ │KS_0 ││KS_1 ││KS_2 ││KS_3 │ (Keyspace / Shard) │
│ │ ││ ││ ││ │ │
│ │ ┌─┐ ││ ┌─┐ ││ ┌─┐ ││ ┌─┐ │ │
│ │ │P │ ││ │P │ ││ │P │ ││ │P │ │ Primary │
│ │ └─┘ ││ └─┘ ││ └─┘ ││ └─┘ │ │
│ │ ┌─┐ ││ ┌─┐ ││ ┌─┐ ││ ┌─┐ │ │
│ │ │R │ ││ │R │ ││ │R │ ││ │R │ │ Replica │
│ │ └─┘ ││ └─┘ ││ └─┘ ││ └─┘ │ │
│ └─┬───┘└─┬───┘└─┬───┘└─┬───┘ │
│ │ │ │ │ │
│ ┌─▼──────▼──────▼──────▼──┐ │
│ │ vttablet │ (每个 MySQL 旁的 sidecar) │
│ │ - 管理 mysqld │ │
│ │ - 在线 DDL │ │
│ │ - 备份/恢复 │ │
│ │ - VReplication │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ etcd (拓扑存储) │ │
│ │ - Keyspace/Shard 拓扑 │ │
│ │ - 路由信息 │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ VTOrc (故障检测+切换) │ │
│ │ - 监控 MySQL 健康 │ │
│ │ - 自动主从切换 │ │
│ └──────────────────────────┘ │
│ │
│ 部署:通常在 Kubernetes 上运行 │
│ vttablet 作为 sidecar 与 mysqld 同 Pod │
│ │
└──────────────────────────────────────────────────────────────┘5.2 核心概念
| 概念 | 类比 | 说明 |
|---|---|---|
| Keyspace | 逻辑数据库 | 一个 Keyspace 可包含多个 Shard |
| Shard | 分片 | Keyspace 的数据子集 |
| VTTablet | 代理 | 每个 MySQL 实例旁的 sidecar 进程 |
| VTGate | 网关 | 接收应用请求,路由到对应 Shard |
| VSchema | 分片规则 | 定义分片键和分片策略 |
| VReplication | 复制 | Vitess 自己的复制机制,支持在线重分片 |
5.3 VSchema 配置示例
{
"sharded": true,
"vindexes": {
"hash": {
"type": "hash"
},
"lookup": {
"type": "consistent_lookup",
"params": {
"table": "user_lookup",
"from": "user_id",
"to": "keyspace_id"
}
}
},
"tables": {
"t_order": {
"column_vindexes": [
{
"column": "user_id",
"name": "hash"
}
],
"auto_increment": {
"column": "order_id",
"sequence": "t_order_seq"
}
},
"t_order_item": {
"column_vindexes": [
{
"column": "user_id",
"name": "hash"
}
]
},
"t_config": {
"type": "reference"
}
}
}5.4 Vitess 在线重分片
┌──────────────────────────────────────────────────────────────┐
│ Vitess 在线重分片流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 初始状态:2个分片 (-80, 80-) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Shard -80 │ │ Shard 80- │ │
│ │ (hash 0~127) │ │ (hash 128~255)│ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 目标状态:4个分片 (-40, 40-80, 80-c0, c0-) │
│ │
│ 步骤: │
│ 1. 创建新分片 │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │-40 │ │40-80 │ │80-c0 │ │c0- │ │
│ │(empty) │ │(empty) │ │(empty) │ │(empty) │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │
│ 2. VReplication 开始从旧分片复制数据 │
│ 旧 -80 → 新 -40 (hash 0~63) │
│ 旧 -80 → 新 40-80 (hash 64~127) │
│ 旧 80- → 新 80-c0 (hash 128~191) │
│ 旧 80- → 新 c0- (hash 192~255) │
│ │
│ 3. 数据同步完成后,切换路由 │
│ VTGate 更新路由表 → 指向新分片 │
│ │
│ 4. 旧分片下线 │
│ 旧 -80 和 80- 标记为只读 → 确认无流量 → 删除 │
│ │
│ ✅ 全程在线,无需停服 │
│ ✅ VReplication 持续同步增量数据,切换时延迟极短 │
│ │
└──────────────────────────────────────────────────────────────┘5.5 Vitess 的优缺点
优点:
- 云原生设计,Kubernetes 集成完美
- 在线重分片(VReplication),不停服扩容
- YouTube/Slack 等超大规模生产验证
- 完善的故障检测和切换(VTOrc)
- CNCF 毕业项目,生态成熟
- 支持 MySQL 协议和 gRPC
缺点:
- 学习曲线陡峭(概念多:Keyspace/Shard/VTTablet/VTGate)
- 部署复杂(依赖 etcd + Kubernetes + 多组件)
- 对非 Kubernetes 环境支持一般
- 中文文档和社区相对弱
- 运维门槛高
六、跨库查询解决方案
6.1 绑定表(Binding Table)
主表和子表使用相同的分片键和分片策略,确保关联数据在同一分片。
┌──────────────────────────────────────────────────────┐
│ 绑定表 (ER 分片) │
├──────────────────────────────────────────────────────┤
│ │
│ t_order 和 t_order_item 都按 user_id 分片 │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ shard_0 │ │ shard_1 │ │
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │
│ │ │t_order │ │ │ │t_order │ │ │
│ │ │user_id │ │ │ │user_id │ │ │
│ │ │ 1004 │ │ │ │ 1001 │ │ │
│ │ │ 1008 │ │ │ │ 1005 │ │ │
│ │ └─────────┘ │ │ └─────────┘ │ │
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │
│ │ │t_order_ │ │ │ │t_order_ │ │ │
│ │ │item │ │ │ │item │ │ │
│ │ │user_id │ │ │ │user_id │ │ │
│ │ │ 1004 │ │ │ │ 1001 │ │ │
│ │ │ 1008 │ │ │ │ 1005 │ │ │
│ │ └─────────┘ │ │ └─────────┘ │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ JOIN 查询:SELECT * FROM t_order o │
│ JOIN t_order_item i ON o.id = i.order_id │
│ WHERE o.user_id = 1001 │
│ │
│ → 只需查 shard_1,无需跨库 JOIN ✅ │
│ │
└──────────────────────────────────────────────────────┘6.2 广播表(Broadcast Table)
数据量小且需要 JOIN 的表,在每个分片都存一份完整副本。
┌──────────────────────────────────────────────┐
│ 广播表 │
├──────────────────────────────────────────────┤
│ │
│ t_config (配置表,100行) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐│
│ │ shard_0 │ │ shard_1 │ │ shard_2 ││
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ ││
│ │ │t_order│ │ │ │t_order│ │ │ │t_order│ ││
│ │ ┌───────┐│ │ │ ┌───────┐│ │ │ ┌───────┐││
│ │ │t_config││ │ │ │t_config││ │ │ │t_config│││
│ │ │(完整) │ │ │ │(完整) │ │ │ │(完整) │ ││
│ │ └───────┘ │ │ └───────┘ │ │ └───────┘ ││
│ └───────────┘ └───────────┘ └───────────┘│
│ │
│ JOIN 时本地完成,无需跨库 │
│ 写入时同步到所有分片 │
│ 适合:字典表、配置表、地区表(数据量小) │
│ │
└──────────────────────────────────────────────┘6.3 跨库 JOIN 的其他方案
┌──────────────────────────────────────────────────────┐
│ 跨库 JOIN 方案对比 │
├──────────────────────────────────────────────────────┤
│ │
│ 1. 绑定表(ER分片) │
│ - 相同分片键的表 JOIN 在同分片完成 │
│ - ShardingSphere / MyCat / Vitess 均支持 │
│ │
│ 2. 广播表 │
│ - 小表全量复制到每个分片 │
│ - 三者均支持 │
│ │
│ 3. 应用层组装 │
│ - 分别查各分片,应用代码中 JOIN │
│ - 通用但代码复杂 │
│ │
│ 4. 冗余字段 │
│ - 在订单表中冗余用户名字段 │
│ - 避免跨库 JOIN │
│ │
│ 5. 数据同步 │
│ - 用 Canal/Debezium 同步到 ES/Redis │
│ - 在搜索引擎中完成复杂查询 │
│ │
│ 6. Vitess 跨分片 JOIN │
│ - VTGate 支持有限的跨分片 JOIN │
│ - 性能取决于数据量和分片键 │
│ │
└──────────────────────────────────────────────────────┘七、分布式事务支持对比
7.1 分布式事务方案
┌──────────────────────────────────────────────────────────────┐
│ 分布式事务方案对比 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 【XA 事务】(强一致) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │App │ │DB_0 │ │DB_1 │ │
│ │ │──│PREPARE │ │PREPARE │ │
│ │ │ │ OK │ │ OK │ │
│ │ │──│COMMIT │ │COMMIT │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ 优点:强一致,不丢数据 │
│ 缺点:性能差(锁定时间长),协调者单点 │
│ │
│ 【BASE 事务 / Saga】(最终一致) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Step 1 │────→│Step 2 │────→│Step 3 │ │
│ │扣库存 │ │创建订单│ │扣余额 │ │
│ └────────┘ └────────┘ └────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │补偿: │ │补偿: │ │补偿: │ │
│ │回库存 │ │删订单 │ │退余额 │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ 优点:性能好,无长时间锁 │
│ 缺点:需要写补偿逻辑,最终一致 │
│ │
│ 【本地消息表】(最终一致) │
│ ┌──────────────────────┐ │
│ │ DB_0 │ │
│ │ ┌──────────────────┐ │ ┌──────────────┐ │
│ │ │ 业务表 │ │ │ MQ / 消息 │ │
│ │ │ + 本地消息表 │─│───→│ │ │
│ │ │ (同一个事务) │ │ └──────┬───────┘ │
│ │ └──────────────────┘ │ │ │
│ └──────────────────────┘ ▼ │
│ ┌──────────────┐ │
│ │ DB_1 │ │
│ │ 消费消息 │ │
│ └──────────────┘ │
│ │
│ 优点:可靠,无全局锁 │
│ 缺点:实现复杂,有延迟 │
│ │
└──────────────────────────────────────────────────────────────┘7.2 三中间件分布式事务对比
| 方案 | ShardingSphere | MyCat | Vitess |
|---|---|---|---|
| XA | ✅ 原生支持(Atomikos/Narayana) | ⚠️ 基础支持(不稳定) | ❌ 不推荐 |
| BASE/Saga | ✅ Seata 集成 | ❌ 不支持 | ❌ 不支持 |
| 本地消息表 | ✅ 支持 | ❌ 需自行实现 | ✅ VReplication |
| 2PC | ✅ 内置 | ⚠️ 弱 | ❌ |
| 最佳实践 | XA + Seata | 避免跨库事务 | VReplication + 应用层 |
| 跨库一致性 | 强一致 / 最终一致 | 弱 | 最终一致 |
⚠️ 避坑:分布式事务性能损耗大。生产环境优先用本地消息表 + 最终一致方案,避免使用 XA。如果必须强一致,ShardingSphere 的 XA 实现最成熟。
八、迁移方案
8.1 从单库迁移到分库分表
┌──────────────────────────────────────────────────────────────┐
│ 双写迁移方案(最常用) │
├──────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1:准备(搭建分片集群) │
│ ┌────────────┐ ┌──────────────────────────┐ │
│ │ 单库 MySQL │ │ 分片集群 (4库×4表) │ │
│ │ (旧) │ │ (新,空数据) │ │
│ └────────────┘ └──────────────────────────┘ │
│ │
│ 阶段 2:全量数据迁移 │
│ ┌────────────┐ ┌──────────────────────────┐ │
│ │ 单库 MySQL │────→│ 分片集群 │ │
│ │ (旧) │ DTS │ (新,历史数据) │ │
│ └────────────┘ └──────────────────────────┘ │
│ 工具:DataX / Canal / CloudCanal / Debezium │
│ │
│ 阶段 3:双写 + 校验 │
│ ┌────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │写旧库 │ │写新库 │ (双写) │ │
│ │ │(主) │ │(从) │ │ │
│ │ └─────────┘ └─────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ ┌────────────┐ ┌──────────────────────────┐ │
│ │ 单库 MySQL │ │ 分片集群 │ │
│ └────────────┘ └──────────────────────────┘ │
│ │ │ │
│ └─────数据校验────────┘ │
│ (定时对比两边数据,确保一致) │
│ │
│ 阶段 4:读流量切换 │
│ ┌────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │写旧库 │ │写新库 │ (双写) │ │
│ │ │读新库 │ │ │ (读切换) │ │
│ │ └─────────┘ └─────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ 灰度:1% → 10% → 50% → 100% │
│ │
│ 阶段 5:停旧库写 │
│ ┌────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ┌─────────┐ │ │
│ │ │写新库 │ (仅新库) │ │
│ │ │读新库 │ │ │
│ │ └─────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ 旧库保持只读一段时间,确认无问题后下线 │
│ │
└──────────────────────────────────────────────────────────────┘8.2 迁移注意事项
┌──────────────────────────────────────────────────┐
│ 迁移避坑清单 │
├──────────────────────────────────────────────────┤
│ │
│ □ 1. 分片键选择:一旦确定,几乎不可更改 │
│ - 选查询最多的字段 │
│ - 避免选会变动的字段(如手机号可能变更) │
│ │
│ □ 2. 自增 ID 冲突 │
│ - 单库 auto_increment 不再适用 │
│ - 改用 Snowflake / UUID / 号段模式 │
│ │
│ □ 3. 外键约束失效 │
│ - 分库后跨库外键不可用 │
│ - 改为应用层保证数据一致性 │
│ │
│ □ 4. 全局唯一性 │
│ - 唯一索引只能在单分片内保证 │
│ - 需要全局唯一的字段考虑用分片键本身 │
│ │
│ □ 5. 跨库查询 │
│ - 提前梳理所有 SQL │
│ - 跨库 JOIN 改为绑定表或应用层组装 │
│ │
│ □ 6. 聚合统计 │
│ - COUNT/SUM 可归并但效率低 │
│ - 考虑将统计数据同步到 ES/Redis │
│ │
│ □ 7. 分页问题 │
│ - LIMIT 10000, 10 需要每个分片取 10010 条 │
│ - 深度分页性能差,用游标分页替代 │
│ │
│ □ 8. 事务边界 │
│ - 尽量将操作限制在同一分片内 │
│ - 跨库事务用最终一致方案 │
│ │
└──────────────────────────────────────────────────┘九、选型建议
9.1 综合对比矩阵
| 维度 | ShardingSphere | MyCat | Vitess |
|---|---|---|---|
| 维护状态 | ✅ 活跃(Apache 顶级项目) | ❌ 停滞 | ✅ 活跃(CNCF 毕业项目) |
| 部署模式 | JDBC + Proxy 双模式 | 仅 Proxy | Proxy + Sidecar |
| 语言支持 | JDBC 仅 Java,Proxy 通用 | 通用 | 通用 |
| 分片策略 | 范围/Hash/一致性Hash/自定义 | 范围/Hash/自定义 | Hash/范围/自定义 |
| 跨库 JOIN | 绑定表 + 广播表 | 绑定表 + 广播表 | 绑定表 + 跨分片JOIN(有限) |
| 分布式事务 | XA + BASE(Seata) | 弱 | VReplication |
| 在线扩容 | ⚠️ 需停服或双写迁移 | ⚠️ 需停服 | ✅ VReplication 在线 |
| 读写分离 | ✅ 内置 | ✅ 内置 | ✅ 内置 |
| 高可用 | 依赖外部(MHA/MGR) | 依赖外部 | ✅ VTOrc 内置 |
| K8s 集成 | ⚠️ 一般 | ❌ 差 | ✅ 原生 |
| 学习曲线 | 中 | 低 | 高 |
| 社区生态 | 国内活跃 | 衰退 | 国际活跃 |
| 中文文档 | ✅ 丰富 | ✅ 丰富 | ❌ 一般 |
| 生产案例 | 国内大量(京东/当当等) | 国内中量 | YouTube/Slack/ pinterest |
| 运维门槛 | 中 | 低 | 高 |
| 适合规模 | 中大型 | 中小型 | 超大型 |
9.2 选型决策树
┌─────────────────────┐
│ 需要分库分表中间件 │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ 运行在 K8s 上? │
└──────┬───────┬──────┘
是 否
│ │
┌────────┘ └──────────────┐
▼ ▼
┌───────────────┐ ┌──────────────────┐
│ 超大规模? │ │ Java 技术栈? │
│ > 100 分片 │ └────┬───────┬─────┘
└──┬────────┬───┘ 是 否
是 否 │ │
│ │ ┌───────┘ └──────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Vitess │ │ShardingSphere│ │ShardingSphere│ │ShardingSphere│
│ │ │ Proxy │ │ JDBC 模式 │ │ Proxy 模式 │
│(云原生) │ │ │ │(性能最优) │ │(多语言支持) │
└──────────┘ └──────────┘ └──────────────┘ └──────────────┘9.3 具体场景推荐
| 场景 | 推荐 | 原因 |
|---|---|---|
| Java 项目,中等规模 | ShardingSphere-JDBC | 零额外部署,性能最优 |
| 多语言项目,中等规模 | ShardingSphere-Proxy | MySQL 协议通用 |
| 超大规模,K8s 环境 | Vitess | 在线扩容,云原生 |
| 老项目已用 MyCat | 迁移到 ShardingSphere | MyCat 维护停滞 |
| 需要强一致分布式事务 | ShardingSphere + Seata | XA/BASE 事务最完善 |
| 快速验证,小规模 | ShardingSphere-JDBC | 最轻量,改配置即可 |
十、面试要点
Q1:ShardingSphere 的 JDBC 模式和 Proxy 模式有什么区别?如何选择?
A:
- JDBC 模式:作为 JAR 包嵌入应用进程,直接连接数据库。优点是零网络开销、性能最优;缺点是只支持 Java,每个实例需独立配置。
- Proxy 模式:独立部署的代理服务,应用通过 MySQL 协议连接。优点是多语言支持、配置统一管理;缺点是多一层网络转发有延迟。
选择原则:Java 项目优先 JDBC;多语言或需要统一管理用 Proxy。也可以组合使用(核心服务用 JDBC,其他用 Proxy)。
Q2:分片键如何选择?
A:分片键选择遵循以下原则:
- 高基数:值分布均匀,避免数据倾斜
- 查询频繁:选查询条件中最常出现的字段
- 不可变性:分片键值不能频繁变更,否则需数据迁移
- 业务关联:优先选能将关联数据路由到同一分片的字段
常见选择:user_id(C 端业务)、order_id(订单系统)、tenant_id(SaaS 多租户)。避免选 create_time(热点)、status(低基数)。
Q3:分库分表后如何处理跨库 JOIN?
A:按优先级:
- 绑定表:有主从关系的表用相同分片键,JOIN 在同分片完成
- 广播表:小表(配置表/字典表)广播到所有分片
- 冗余字段:在主表中冗余常用字段,避免 JOIN
- 应用层组装:分别查询,代码中组装
- 数据同步:用 Canal 将数据同步到 ES,在 ES 中完成复杂查询
- Vitess 跨分片 JOIN:VTGate 支持有限跨分片 JOIN,但性能取决于数据量
Q4:分库分表后分页查询有什么问题?
A:深度分页问题。例如 LIMIT 100000, 10,需要每个分片取前 100010 条,合并后再排序取 10 条。分片数越多,查询越慢。
解决方案:
- 游标分页:用
WHERE id > last_id LIMIT 10替代 OFFSET - 禁止深度跳页:只允许"上一页/下一页"
- 二次查询法:先查各分片最大 ID,再精确查询
- 搜索引擎:将数据同步到 ES,利用 ES 的分页能力
Q5:Vitess 的 VReplication 有什么优势?
A:VReplication 是 Vitess 的核心复制机制,支持:
- 在线重分片:扩容/缩容时不停服,VReplication 自动同步数据到新分片
- 物化视图:可以从多个源表按需构建视图
- 跨 Keyspace 同步:不同 Keyspace 之间的数据复制
- 在线 Schema 变更:DDL 通过 VReplication 在线执行,不阻塞写入
相比 ShardingSphere 和 MyCat 的扩容需要停服或双写迁移,Vitess 的在线重分片是最大优势。
Q6:分库分表后全局唯一 ID 怎么生成?
A:常见方案:
| 方案 | 原理 | 优缺点 |
|---|---|---|
| Snowflake | 时间戳+机器ID+序列号 | 趋势递增,需解决时钟回拨 |
| 号段模式 | DB 批量取号段,应用内分配 | 依赖 DB,但压力小 |
| Redis INCR | 原子自增 | 简单,但依赖 Redis 可用性 |
| UUID | 随机唯一值 | 无序,B+树写入差 |
| 数据库自增+步长 | 每个分片不同起始值和步长 | 简单,但扩容困难 |
生产推荐:Snowflake(ShardingSphere 内置)或号段模式(美团 Leaf)。
Q7:ShardingSphere、MyCat、Vitess 的核心区别?
A:
- ShardingSphere:Apache 顶级项目,JDBC+Proxy 双模式,Java 生态最友好,分布式事务支持最完善(XA+Seata)。适合中大型 Java 项目。
- MyCat:早期 Proxy 中间件,配置简单,但社区停滞、SQL 兼容性差、事务弱。不推荐新项目使用。
- Vitess:CNCF 毕业项目,云原生设计,支持在线重分片(VReplication),K8s 深度集成。适合超大规模、K8s 环境的项目,但运维门槛高。
Q8:什么情况下不需要分库分表?
A:以下情况可以不分:
- 数据量 < 1000 万:优化索引和查询即可
- 读写 QPS < 1000:读写分离+缓存可以解决
- 单表 < 5000 万 + 合理索引:MySQL 单表性能没你想的那么差
- 可以冷热分离:热数据留在主表,冷数据归档到历史表
- 可以用 TiDB 等 NewSQL:分布式数据库原生支持水平扩展,不需要中间件
分库分表是最后的手段,不是第一选择。先穷尽单库优化手段(索引、分区表、读写分离、缓存、冷热分离),再考虑分库分表。
十一、总结
分库分表是数据库架构演进的必经之路,但也是一把双刃剑——解决了性能问题,带来了跨库查询、分布式事务、运维复杂度等新挑战。
三大中间件各有定位:
ShardingSphere:国产之光,Java 生态首选,JDBC 模式性能最优,Proxy 模式多语言通用。Apache 顶级项目,持续维护,社区活跃。新项目中优先推荐。
MyCat:曾经的国民中间件,但社区衰退,SQL 兼容性和事务能力弱。老项目建议规划迁移到 ShardingSphere。
Vitess:云原生 MySQL 集群管理的天花板。在线重分片是杀手锏,K8s 集成最完善。适合超大规模、技术能力强的团队。
核心原则:
- ✅ 分片键选择要慎重,几乎不可逆
- ✅ 优先穷尽单库优化,分库分表是最后手段
- ✅ 跨库 JOIN 提前规划(绑定表/广播表/冗余字段)
- ✅ 分布式事务优先用最终一致方案
- ✅ 迁移走双写方案,灰度切换
- ✅ 考虑 NewSQL(TiDB/OceanBase)作为替代方案
记住:分库分表不是终点,而是新的起点。分完之后,监控、运维、扩容、数据一致性,每一步都是挑战。选择合适的中间件,让挑战变得可控。