MyCat2 使用记录与放弃复盘

MyCat2 使用记录与放弃复盘

做分库分表选型,最先评估的就是 MyCat2。吸引我的地方很直接:代理模式,应用像连普通 MySQL 一样连 MyCat,业务代码不需要改动。团队当时对 ShardingJDBC 的配置复杂度有顾虑,所以 MyCat2 排在第一位评估。

结论先说:搭建验证通过了,但跑现有业务 SQL 发现了几个无法绕过的限制,最终放弃,改用 ShardingJDBC


一、为什么先评估 MyCat2

分库分表主流方案的核心差异只有一个维度:代理模式 vs JDBC 层拦截

  • MyCat2(代理模式):应用连 MyCat,MyCat 连后端数据库,应用看到的是「一个普通 MySQL」,业务代码零改动
  • ShardingJDBC(JDBC 层):以 JAR 包集成进应用,在 JDBC 层拦截 SQL 做路由改写,需要引入依赖和配置

选 MyCat2 的理由:不改业务代码,迁移成本低;支持多语言项目(PHP 老服务也能用)。

选 ShardingJDBC 的理由:进程内处理,无额外网络跳转,性能更好;SQL 兼容性更强;Java 生态支持最完善。


二、搭建过程

环境

  • MyCat2 版本:1.22-release
  • 后端数据库:MySQL 8.0,2 个实例(3306/3307)
  • 操作系统:CentOS 7,Docker 部署

核心步骤

1. 下载并配置原型库(prototype)

MyCat2 用一个 MySQL 实例作为元数据库(prototype),存储逻辑表配置:

# 下载 MyCat2
wget https://github.com/MyCATApache/Mycat2/releases/download/1.22-release/mycat2-install-template-1.22.jar
java -jar mycat2-install-template-1.22.jar

# 修改 conf/datasources/prototype.datasource.json
{
"dbType": "mysql",
"url": "jdbc:mysql://127.0.0.1:3306/mycat",
"user": "root",
"password": "123456"
}

2. 配置后端物理库

// conf/datasources/ds0.datasource.json
{
"dbType": "mysql",
"url": "jdbc:mysql://127.0.0.1:3306/order_db",
"user": "root",
"password": "123456"
}

// conf/datasources/ds1.datasource.json
{
"dbType": "mysql",
"url": "jdbc:mysql://127.0.0.1:3307/order_db",
"user": "root",
"password": "123456"
}

3. 启动并验证

cd mycat2/bin
./mycat start

# 连接 MyCat(端口 8066)
mysql -h 127.0.0.1 -P 8066 -u root -p123456

分片配置示例

在 MyCat 控制台用 DDL 注解声明分片规则:

-- 创建逻辑库
CREATE DATABASE mycat_order;

-- 创建逻辑表,通过注解声明分片规则
CREATE TABLE `t_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`amount` decimal(10,2) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
dbpartition by MOD_HASH(user_id) dbpartitions 2
tbpartition by MOD_HASH(user_id) tbpartitions 4;

执行后 MyCat 会自动在后端数据库创建 t_order_0 ~ t_order_3 物理表。

基础的增删改查确实没问题,连接 MyCat 的方式和连普通 MySQL 完全一样,应用代码不需要任何修改。


三、遇到的 SQL 限制

基础功能跑通之后,开始把现有业务 SQL 在 MyCat 上跑测试,陆续遇到几个问题。

问题一:跨分片 JOIN

业务里有一个查询:订单表 JOIN 用户表,需要同时展示订单信息和用户名称。t_orderuser_id 分片,t_user 没有分片,两个表的分片键不同。

-- 这条 SQL 在 MyCat2 上执行报错
SELECT o.id, o.amount, u.username
FROM t_order o
JOIN t_user u ON o.user_id = u.id
WHERE o.create_time > '2023-01-01'

报错:ERR-CODE: [MYCAT-60002], Unsupported cross-shard join

尝试用 MyCat 的 ER 关系表配置来绑定两个表,但 t_user 是未分片的全局表,配置后仍然无法正常路由。

问题二:子查询中的分片键推断失败

-- 嵌套子查询,MyCat 无法推断外层的分片键路由
SELECT * FROM t_order
WHERE user_id IN (
SELECT user_id FROM t_user WHERE status = 1
)

MyCat 对这类 SQL 会做全分片广播,性能退化成扫全表。数据量大时直接超时。

问题三:ORDER BY + LIMIT 分页的内存问题

-- 跨分片分页,MyCat 需要在内存里合并排序
SELECT * FROM t_order ORDER BY create_time DESC LIMIT 1000, 20

MyCat 的处理方式是:从每个分片取 LIMIT 1020 条数据到内存合并排序,再截取第 1000-1020 条。分片数越多,合并的数据量越大。分页偏移量越大,内存压力越大。

这个查询是商家后台的订单列表接口,OFFSET 会很大,无法使用。


四、最终放弃的原因

遇到这三个问题之后,评估了改造方案:

  1. 跨分片 JOIN:改成应用层关联——先查订单,再批量查用户信息,在代码里组装。改造量不大,但需要修改所有涉及的 Service 层方法。
  2. 子查询:把 IN 子查询改成 JOIN 或者拆成两次查询。能改,但散落在项目各处,改动量大。
  3. 分页排序:必须改成基于游标的分页(记录上次的 create_timeid 作为游标),彻底改变分页方式,前端也要配合修改。

评估之后得出结论:为了适配 MyCat2 的 SQL 限制,需要修改的代码量远超引入 ShardingJDBC 的接入成本。ShardingJDBC 以 JAR 包集成,SQL 兼容性更好,那些 SQL 基本不需要修改就能跑通。

放弃 MyCat2 不是它不好,而是它和我们项目的 SQL 模式不匹配。如果是新项目从一开始就按 MyCat 的约束设计 SQL,或者是多语言混合的项目,MyCat2 的优势才能发挥出来。


五、MyCat2 vs ShardingJDBC 选型对比

维度MyCat2ShardingJDBC
接入方式代理模式,应用改动小JDBC 层,需引入 JAR + 配置
性能多一层网络代理,有额外损耗进程内处理,性能更好
SQL 兼容性受限较多,复杂 SQL 支持弱支持更广泛
运维复杂度MyCat 独立部署,需维护中间件无独立中间件,应用自包含
跨分片 JOIN支持有限(需要 ER 关系绑定)支持绑定表等优化手段
社区活跃度较弱,更新缓慢ShardingSphere 社区活跃
适合场景多语言项目、SQL 简单、不想改代码Java 项目、现有复杂 SQL、追求性能

选型建议:Java 项目直接选 ShardingJDBC,MyCat2 适合的是「多语言、SQL 简单、想要对业务零侵入」的场景。