附-02 · MySQL 分库分表与中间件 —— ShardingSphere / MyCat 分片策略实战
系列定位:《MySQL 数据库全栈运维实战系列》第 附-02 篇(番外),与《Oracle 数据库全栈运维实战系列(VMware 版)》同构对标。适用版本:MySQL 8.0(5.7 差异随文标注)。官方文档:Apache ShardingSphere 官方文档、MyCat 开源社区文档、MySQL 8.0 Reference Manual。
一、什么时候该分库分表
图 1
单表超过 2000 万行、单库 TPS 超过单机上限、或者业务按租户天然隔离时,才需要分库分表。分库分表是最后手段,不是银弹——它会带来分布式事务、跨分片 JOIN、扩容迁移、全局唯一 ID 一系列复杂度。能靠索引优化、读写分离、缓存解决的,先别分。
图 2
| 方案 | 代表 | 类比 Oracle |
|---|---|---|
| 垂直拆分 | 按业务拆库(订单库/用户库) | Oracle Schema 分离 |
| 水平分库 | 同一张表按分片键散到多个库 | Oracle 无原生等价,靠分区表 |
| 水平分表 | 单库内分多张表 | Oracle 分区表 Partition |
| 中间件 | ShardingSphere / MyCat | Oracle 无对应产品,属架构层 |
VMware 虚机环境注意事项:分片后的每个分片都是一台独立 MySQL 实例(或独立虚机),虚机数量线性增长。测试环境建议 2~3 个分片节点 + 1 个中间件节点,全部跑在 VMware 上。中间件节点(ShardingSphere-Proxy/MyCat)本身无状态,可双实例 + VIP 高可用。分片节点的磁盘要各自独立 VMDK,不要多个分片挤一块盘。
二、两大中间件对比
| 维度 | ShardingSphere-JDBC | ShardingSphere-Proxy | MyCat(1.6/2.x) |
|---|---|---|---|
| 形态 | JDBC 客户端库,嵌应用内 | 独立代理服务 | 独立代理服务 |
| 侵入性 | 应用引 jar,改配置 | 零侵入,改连接串 | 零侵入 |
| 多语言 | 仅 Java | 任意语言 | 任意语言 |
| 性能 | 最好(无网络跳转) | 中间多一跳 | 中间多一跳 |
| 运维 | 配置散在各应用 | 集中管理 | 集中管理 |
| 社区 | Apache 顶级项目,活跃 | 同左 | 1.6 停更,2.x 社区弱 |
| 推荐度 | Java 业务首选 | 异构语言/统一运维首选 | 老系统存量,新项目不建议 |
三、分片策略怎么选
图 3
图 4
| 策略 | 适用 | 优点 | 缺点 |
|---|---|---|---|
| 取模 id mod N | 等值查询均匀 | 数据均匀、热点均衡 | 扩容要迁移大量数据 |
| 范围分片(按时间) | 订单/日志按天归档 | 易归档、易冷热分离 | 热点(最近月份写集中) |
| 一致性哈希 | 动态扩缩容 | 扩容只迁相邻节点 | 实现复杂、数据略不均 |
| 按枚举(省/市/租户) | 天然隔离的业务 | 业务清晰 | 可能数据倾斜 |
分片键选择三原则:
- 绝大多数 SQL 都带这个键(否则全分片广播,性能灾难)。
- 数据分布均匀(别拿性别、地区做取模分片键)。
- 尽量不更新(分片键改了要跨分片迁移行)。
四、ShardingSphere-JDBC 5.x 实战
4.1 Maven 依赖
<!-- pom.xml(解释:5.x 稳定版,spring-boot-starter 更省事) --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.4.1</version> </dependency>4.2 YAML 分片配置(t_order 按 user_id 取模分 4 片)
# sharding.yml(解释:4 个真实库 ds0~ds3,t_order 按 user_id 取模路由) dataSources: ds0: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://db-shard-0:3306/order_ds0?useSSL=false&serverTimezone=Asia/Shanghai username: shard password: shard_pwd ds1: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://db-shard-1:3306/order_ds1 username: shard password: shard_pwd ds2: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://db-shard-2:3306/order_ds2 username: shard password: shard_pwd ds3: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://db-shard-3:3306/order_ds3 username: shard password: shard_pwd rules: - !SHARDING tables: t_order: actualDataNodes: ds0..3.t_order_0..16 databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: db_mod tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: table_mod keyGenerateStrategy: column: order_id keyGeneratorName: snowflake shardingAlgorithms: db_mod: type: MOD props: sharding-count: 4 table_mod: type: MOD props: sharding-count: 17 keyGenerators: snowflake: type: SNOWFLAKE-- 业务侧无感知(解释:应用还是写 t_order,中间件自动路由) INSERT INTO t_order (user_id, order_id, amount) VALUES (1001, 20261009001, 99.5); -- ShardingSphere 自动算:user_id=1001 对 4 取余为 1 路由到 ds1;order_id 对 17 取余为 8 路由到 t_order_08 -- 实际落在 ds1.t_order_08五、ShardingSphere-Proxy 实战(独立代理)
Proxy 跑在独立节点,应用把它当普通 MySQL 连。
# 下载启动(解释:5.x 发行包解压即用,默认 3307 端口) tar -zxvf apache-shardingsphere-5.4.1-shardingsphere-proxy-bin.tar.gz -C /opt/ cd /opt/apache-shardingsphere-proxy-bin/ # 配置 conf/server.yaml 和 conf/config-sharding.yaml(结构同 JDBC 版 YAML) ./bin/start.sh # 应用侧连 Proxy(解释:和连普通 MySQL 没区别) mysql -h proxy-node -P 3307 -u sharding -p图 5
六、MyCat 1.6 配置(存量系统参考)
MyCat 1.6 的经典配置由 schema.xml 和 rule.xml 两部分组成。
6.1 schema.xml(逻辑表与数据节点)
<schema name="orders_db"> <table name="t_order" primaryKey="order_id" dataNode="dn1,dn2,dn3,dn4" rule="order-by-user-id"></table> </schema> <dataNode name="dn1" dataHost="host0" database="order_ds0"/> <dataNode name="dn2" dataHost="host1" database="order_ds1"/> <dataNode name="dn3" dataHost="host2" database="order_ds2"/> <dataNode name="dn4" dataHost="host3" database="order_ds3"/>6.2 rule.xml(取模分片规则)
<tableRule name="order-by-user-id"> <rule> <columns>user_id</columns> <algorithm>mod-four</algorithm> </rule> </tableRule>对应的分片算法定义在 rule.xml 的 function 段:name 为 mod-four,实现类是 MyCat 内置的取模函数 PartitionByMod(完整包路径 org.opencloudb.route.function.PartitionByMod),参数 count=4,即按 user_id 对 4 取余路由到 4 个数据节点。
注意:MyCat 1.6 已多年不维护,是 JDK 7/8 时代产物,新项目不建议选;存量维护可参考,新系统一律 ShardingSphere 5.x。
七、分库分表的四大坑
7.1 跨分片查询
不带分片键的 SQL 会广播到所有分片,性能差。
-- 带分片键:单分片路由,快 SELECT * FROM t_order WHERE user_id = 1001 AND order_id = 20261009001; -- 不带分片键:全分片广播,慢(解释:必须避免) SELECT * FROM t_order WHERE create_time >= '2026-10-01';解决:这种分析类需求走 ES / 列式库 / 单独的报表库,不要在分片库上跑。
7.2 分布式事务
跨分片写订单 + 库存,本地事务保不住。
| 方案 | 说明 |
|---|---|
| 最终一致(推荐) | 本地消息表 / Canal 订阅 binlog,业务层对账补偿 |
| XA 强一致 | ShardingSphere 支持 XA,但性能差,少用 |
| Seata AT | 微服务体系常用,和 ShardingSphere 可集成 |
7.3 全局唯一 ID
自增主键在分库后会冲突。
| 方案 | 说明 |
|---|---|
| Snowflake(雪花算法) | ShardingSphere 内置,64bit:时间戳+workerId+序列号 |
| 号段模式 | 每个分片段式取号,DB 存 max_id |
| UUID | 索引不友好,InnoDB 主键尽量别用 |
7.4 扩容数据迁移
4 片扩到 8 片,取模规则变了,要重新分布数据。
图 6
工具:ShardingSphere-Scaling、DataX、自研迁移脚本。扩容期间必须双写 + 灰度切流。
八、类比 Oracle 速查
| MySQL 分库分表 | Oracle 对应 | 差异 |
|---|---|---|
| 水平分表 | Oracle Partition(RANGE/LIST/HASH) | Oracle 原生分区在单库内,MySQL 要中间件跨库 |
| 垂直拆库 | Schema/业务拆库 | 类似 |
| 分布式 ID | Oracle Sequence | MySQL 自增不能跨分片,要雪花/号段 |
| 跨分片 JOIN | 分区裁剪 + 本地 JOIN | MySQL 跨库 JOIN 要中间件归并,性能差 |
| ShardingSphere | 无官方等价 | Oracle 靠分区 + RAC 扩展,原生能力更强 |
九、踩坑记录(实测汇总)
| # | 坑 | 现象 | 解决 |
|---|---|---|---|
| 1 | 分片键选错(性别) | 数据倾斜到 1~2 个分片 | 重新选 user_id/order_id 均匀键 |
| 2 | SQL 不带分片键 | 全分片广播,慢 100 倍 | 业务规范强制带分片键 |
| 3 | 用 UUID 当主键 | InnoDB 页分裂严重,写入慢 | 用 Snowflake 有序 ID |
| 4 | 取模分片扩容 | N=4 扩 N=8 要迁一半数据 | 一次规划够 / 一致性哈希 / 双写迁移 |
| 5 | 跨分片分布式事务用 XA | 性能掉一半 | 改最终一致 + 本地消息表 |
| 6 | MyCat 1.6 新项目 | 社区停更,bug 无人修 | 用 ShardingSphere 5.x |
| 7 | Proxy 单点 | 代理挂了全站断 | Proxy 双实例 + VIP/负载均衡 |
| 8 | Snowflake 时钟回拨 | 发重复 ID | workerId 绑定 + 时钟回拨检测报错 |
| 9 | 跨分片 ORDER BY LIMIT | 排序归并耗内存 | 加索引 + 禁止跨深分页 |
| 10 | 分片后没做数据迁移校验 | 新老库数据不一致 | 行数+checksum 双向校验后再切流 |
十、本文要点速记
- 分库分表是最后手段:先索引、读写分离、缓存,顶不住再分。
- 中间件选 ShardingSphere 5.x:Java 用 JDBC 嵌应用,异构用 Proxy;MyCat 只维护存量。
- 分片键三原则:多数 SQL 带它、数据均匀、不更新。
- 取模 vs 范围:等值查询取模均匀,时间归档范围易冷热分离。
- 四大坑提前规划:跨分片查询、分布式事务、全局 ID、扩容迁移。
- 扩容双写 + 校验:别直接切,灰度切流。
十一、参考资料(官方正式文档)
| 主题 | 官方链接 |
|---|---|
| Apache ShardingSphere 官方文档 | https://shardingsphere.apache.org/document/current/cn/overview/ |
| ShardingSphere-JDBC | https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/ |
| ShardingSphere-Proxy | https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-proxy/ |
| ShardingSphere 分片策略 | https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/ |
| ShardingSphere-Scaling(数据迁移) | https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-scaling/ |
| MyCat 开源社区 | https://github.com/MyCATApache/Mycat2 |
| MySQL 8.0 Reference Manual | https://dev.mysql.com/doc/refman/8.0/en/ |
| Snowflake 主键生成 | https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/key-generator/ |
本文章属于《MySQL 数据库全栈运维实战系列》番外篇。欢迎收藏、交流。