ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

mycat2基础安装包:分库分表配置与路由实战

mycat2基础安装包:分库分表配置与路由实战

简介:面向 Java 后端开发与数据库运维人员的 Mycat2 部署入门包,用于快速搭建分布式数据库中间件的实验环境。Mycat2 负责数据分片、SQL 路由、读写分离与负载均衡,这个包提供可直接解压运行的完整目录,便于在本地或远程服务器上验证分片规则、节点状态与故障切换机制。压缩包共 51 个文件,约 1.2MB;其中包含 9 个 sql 初始化脚本、6 个 json 与 xml 配置、覆盖 Linux、Windows、macOS 等多平台的 so、dll、jnilib 本地库,以及 mycat、mycat.bat 等启动脚本和 wrapper 组件。另外还有 schemas、datasources、clusters、sequences 等 conf 配置内容,结构清晰,便于按模块查阅和调整。目前已有 1072 人学习,适合作为 Mycat2 入门验证与二次配置的基础参考,也可用于对比不同分片策略、观察 SQL 执行日志与数据节点运行情况。

1. 从一次「连不上 8066」说起:mycat2 基础安装包解决的是什么问题

前段时间帮人看一个查询超时的现场:后端两台 MySQL,一台 QPS 打满,一台几乎空转,典型的只做了读写分离、没做分片。这类场景里,mycat2 基础安装包往往是最省事的第一步——解压、改四份配置、起 8066,应用侧连的还是 MySQL 协议,业务代码基本不用动。它干的事是把物理分片藏在逻辑库、逻辑表后面,让应用像用单库一样用多库,解决的是单表体量和连接数的天花板。适合正在给几千万行的老表找拆库方案、又不想直接上重型分布式数据库的团队,也适合想先把分库分表跑通再谈治理的个人开发者。反过来,SQL 花样特别多、对跨库强一致有硬要求的业务,得先掂量中间件的兼容边界。下面按「配置怎么组织 → 装起来 → 拆一张表 → 踩坑 → 验证」的顺序拆。

2. mycat2 的配置体系:为什么把 schema.xml 换成了 YAML 与 JSON

拿到安装包先别急着敲启动脚本。mycat2 和 1.x 最大的差别不在功能表上,而在配置形态:1.x 那套schema.xml、rule.xml、server.xml三件套在 2.x 里被拆成了server.yaml加若干份 json。不理解这个拆分,后面每改一处都会怀疑人生。

2.1 Calcite 接管 SQL 解析之后,配置为什么要跟着变

1.x 的路由靠 XML 里手写的算法声明,SQL 进来自家解析器拆一遍,规则表一长就容易出对不上的情况。2.x 把 SQL 解析交给了 Calcite,路由计算建立在元数据之上——也就是说中间件得清楚每张逻辑表长什么样、分片键是哪列、物理表在哪个数据源上。这些信息要能被程序读写、能在线增删,XML 那种「改完必须重启、结构还难校验」的形态就不合适了。

换成 YAML + JSON 之后有个直接好处:建库建表可以走注解 SQL。连上 8066 敲一条带注解的语句,就能动态加数据源、加逻辑库,不用停机改文件。代价是配置文件从「一份大 XML」变成了「一堆小文件」,谁管谁、谁先加载,得自己捋清楚。我一般会在动手前先把conf目录整个看一遍,看清楚哪几个文件是安装包自带的示例、哪几个是真正生效的。示例文件别删,它是你校验字段名对不对的唯一参照。

还有一个隐性成本:分片算法。1.x 的rule.xml里配algorithm加algorithmExpression,2.x 改成在 schema 里写function表达式,语法不一样,老配置搬不过来。如果你手上有一套跑着 1.x 的系统,迁移的大头不在安装,而在把每个分片规则重新翻译一遍,并且逐条用 EXPLAIN 验证路由结果和原来一致。

2.2 四类配置各管什么:别改错文件

文件管什么改动后是否需要重启
conf/server.yaml服务端口、字符集、事务类型、集群与全局开关端口类必须重启,部分开关支持重载
conf/users/*.user.json登录用户、密码、可访问的逻辑库列表新建用户通常重载即可
conf/datasources/*.datasource.json后端物理库的连接串、账号、连接池上下限连接串改动需重启
conf/schemas/*.schema.json逻辑库、逻辑表、分片键、分片算法、物理表映射新增逻辑表多数可热加载

加载关系是自下而上的:数据源先能被连上,schema 才有地方挂物理表;schema 建好了,user 里的schemas才有意义。所以排错顺序也应该反过来——应用连不上,先看 user 里的库名写没写错,再看 schema 有没有真的加载,最后才怀疑数据源。

提示:conf目录里带.json后缀的文件名不要随意改,文件名和内部name字段对不上时,加载行为在不同小版本上表现不一致,很容易查半天。

2.3 装之前先把环境对齐:JDK 与后端认证插件

这一步跳过的人,后面十有八九会在启动日志里看到一堆看不懂的异常。mycat2 是 Java 服务,对 JDK 版本敏感;后端如果是 MySQL 8,默认认证插件也可能直接把连接挡在门外。先花两分钟确认环境:

# 1. 确认 JDK 版本,建议 8 或 11,高版本要先小流量验证 java -version # 2. 确认后端 MySQL 可达,并看清版本 mysql -h 192.168.1.10 -P 3306 -uroot -p -e "select version();" # 3. 如果后端是 MySQL 8,把中间件用的账号切成兼容性更好的认证方式 mysql -h 192.168.1.10 -P 3306 -uroot -p -e " ALTER USER 'mycat_rw'@'%' IDENTIFIED WITH mysql_native_password BY '换成你的密码'; FLUSH PRIVILEGES;" # 4. 确认字符集与时区,避免中文乱码和时间字段偏移 mysql -h 192.168.1.10 -P 3306 -uroot -p -e " show variables like 'character_set_server'; show variables like 'lower_case_table_names'; show variables like 'time_zone';"

java -version输出里如果主版本号是 17 及以上,先别急着往下走,用一个小库跑通全流程再说,反射相关的报错在启动阶段不一定暴露,往往要等到第一次执行带分片的 SQL 才炸。第三步的ALTER USER只影响中间件使用的那个账号,业务账号可以不动;这一步不做,后面日志里大概率是一句语焉不详的认证失败。第四步的lower_case_table_names尤其关键——中间件侧和后端不一致时,建表能成功,查询却报表不存在,这种问题靠猜是猜不出来的。

3. 安装包落地:从解压到第一条 SQL 跑通

环境对齐之后就是常规动作了。这一段没什么玄学,但顺序错了会很折磨人:先起服务再想配置,等于给自己制造盲区。

3.1 目录结构与启动脚本:先看清楚再敲命令

# 解压到统一目录,路径里不要带空格和中文 mkdir -p /opt/middleware unzip mycat2-basic.zip -d /opt/middleware/ cd /opt/middleware/mycat # 看一眼目录构成,心里有数 ls -l # bin/ 启动、停止、状态脚本 # conf/ 全部配置,重点看这里 # lib/ 依赖 jar,不要手动增删 # logs/ 运行日志,排错第一现场 # Linux 下脚本默认可能没有执行权限,补上 chmod +x bin/* # 先看状态,确认当前没在跑 bin/mycat status

bin/mycat一般支持start、stop、restart、status、console几个子命令。日常用start放后台,调配置阶段我建议用console前台启动——日志直接刷在终端上,改一个字段重启一次,反馈最快,不用去logs目录里翻。chmod那一步看着多余,但压缩包跨平台传递之后权限位丢失是常事,报「权限不够」的时候人容易往别处想。

lib目录不要手动塞 jar。有人为了让中间件连上某个特殊版本的驱动,把 jar 丢进去覆盖,结果类冲突报错堆了几十行。驱动版本要换,走正规依赖管理,别在这个目录上做实验。

3.2 三个最小配置写完再启动

第一次启动只需要三份东西:一个后端数据源、一个逻辑库、一个能登录的用户。先写数据源:

{ "name": "ds0", "dbType": "mysql", "instanceType": "JDBC", "url": "jdbc:mysql://192.168.1.10:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false", "user": "mycat_rw", "password": "换成你的密码", "minCon": 2, "maxCon": 100, "maxIdleCon": 20, "idleTimeout": 60000, "initSql": "select 1", "readOnly": false }

name是这份数据源在中间件里的唯一标识,schema 里要靠它找物理库,取名尽量带业务含义。url里的serverTimezone建议显式写死,不写的话时间字段在某些组合下会整体偏移八小时,排查起来非常费劲。maxCon是单数据源的最大连接数,不是全局的,两个分片库各配 100 就是 200,加中间件自身开销,后端max_connections要留足余量。minCon别设 0,冷启动时首批请求会明显变慢。

接着写逻辑库,最小版本只需要名字和默认数据源:

{ "name": "order_logic", "defaultTargetName": "ds0", "customTables": {}, "globalTables": {}, "shardingTables": {} }

name就是应用连接后show databases看到的库名,也是 user 配置里要引用的名字。分片表先留空,等连通性验证完再往里加,一次只改一个变量。

最后是用户:

{ "username": "app_user", "password": "换成你的密码", "ip": "*", "transactionType": "xa", "schemas": ["order_logic"] }

schemas是个数组,写的是逻辑库名,不是后端物理库名,这里写错是最常见的「连上了但看不到库」。ip生产环境不要留*,按网段收紧。transactionType决定跨分片事务的处理方式,本地事务够用的场景可以先按默认走,涉及多库写入再评估。

注意:三份 json 的字段名在不同小版本上偶有增删,写之前对照conf目录里自带的示例文件核一遍,比照文档抄更靠谱。

3.3 连库验活:8066 和 9066 是两回事

# 前台启动,观察日志直到出现监听成功 bin/mycat console # 另开一个终端,用 mysql 客户端连业务端口 mysql -h127.0.0.1 -P8066 -uapp_user -p # 进去之后先做最小验证 show databases; use order_logic; select 1;

能show databases出来order_logic,说明用户、逻辑库、数据源这条链已经通了。这时候show tables大概率是空的,属于正常现象——逻辑表还没配,中间件不会去物理库反射表结构。别在这个阶段怀疑连接有问题。

8066 是业务端口,应用连这个;9066 是管理端口,用来做运维类操作,具体命令各版本差异较大,以安装包内说明为准。两个端口别混用,用管理端口跑业务 SQL 会得到一堆莫名其妙的错误。

4. 分库分表配置实战:把一张订单表拆到两个库

连通性没问题,接下来才是这个安装包真正值钱的地方——让一张逻辑表落到多个物理库上。这里翻车的概率远高于安装阶段,因为错一步不会报错,只会「结果对但很慢」或者「结果少了一半」。

4.1 物理表先建好:DDL 不归中间件管

中间件不会替你建物理表,两个分片库里的表结构必须完全一致:

-- 在 192.168.1.10:3306 的 order_db_0 和 order_db_1 上分别执行 CREATE TABLE orders ( order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id), KEY idx_user (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

分片键我选了order_id,不是user_id。理由是订单类查询九成以上都带订单号,按order_id取模能做到单库命中;如果按user_id分,查订单号就得全路由。这里没有标准答案,判断依据是「最高频的查询条件带哪一列」。索引也要跟着分片键设计,order_id是主键天然带索引,user_id单独留一个普通索引应对用户维度查询。

两个库的表名可以相同,中间件靠数据源区分;表名不同反而要在 schema 里做额外映射,没必要给自己加活。

4.2 schema.json 的字段逐个填

{ "name": "order_logic", "defaultTargetName": "ds0", "customTables": {}, "globalTables": {}, "shardingTables": { "orders": { "createTableSQL": "CREATE TABLE orders (order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, amount DECIMAL(12,2), status TINYINT, created_at DATETIME, PRIMARY KEY(order_id))", "function": "mod_hash", "columns": ["order_id"], "tableCount": 2, "shardingQuery": "mod_hash(order_id, 2)", "targetNames": ["ds0", "ds1"] } } }

几个字段值得单独说。function是分片算法名,mod_hash表示取模散列,同族还有按范围、按日期切的算法,选哪种取决于数据增长形态——订单这种持续写入、按 ID 查为主的场景,取模最省心;如果是按月份归档的日志表,范围切更合适。columns必须写分片键,写错列名不会报错,但路由结果会静默变成全路由。tableCount要和后端实际的分片数对上,填 2 而后端只有 1 个库,写入会在一半的请求上失败。targetNames是物理数据源列表,顺序和取模结果的对应关系要自己确认一遍,别假设。

createTableSQL不是给中间件执行用的,它用于让 Calcite 知道表的字段类型和长度,从而做类型推导和 SQL 校验。字段类型写窄了,可能出现「明明数据能插进去,查询却报类型不匹配」。

提示:改完 schema 后先restart再验证,热加载在大改表结构时不一定完全生效,重启一次能省掉一轮误判。

4.3 用 EXPLAIN 验证路由,而不是靠猜

-- 带上分片键,应该只命中一个物理库 EXPLAIN SELECT * FROM orders WHERE order_id = 10086; -- 不带分片键,会全路由,这是预期行为不是 bug EXPLAIN SELECT * FROM orders WHERE status = 1; -- 插入也看一眼目标库 EXPLAIN INSERT INTO orders(order_id, user_id, amount) VALUES (10086, 7, 99.00);

EXPLAIN的输出里重点看两处:一是最终下发的物理 SQL,二是命中的数据源和物理表名。带order_id的查询应该只出现一个库,出现两个就说明分片配置没生效或者字段没对上。不带分片键的查询必然全路由,这时候要看的是中间件有没有做结果归并、归并后的排序是否正确——数据量大的时候,全路由查询就是性能黑洞,得从业务侧改 SQL,而不是指望中间件优化。

验证顺序建议是:单条插入 → 按分片键查 → 按非分片键查 → 跨库聚合(count、group by)。每步都过一次EXPLAIN,出问题时能立刻定位到是哪一类路由出了偏差,比事后翻日志高效得多。

5. 避坑与排查:五个把我卡到半夜的现场

这一段是按「现象 → 原因 → 解决」整理的实录,都是我或者身边人真撞过的。装中间件这件事,能一次跑通的比例其实不高,大部分时间花在排错上。

5.1 启动直接退出,日志里是版本号相关的异常

现象:bin/mycat start回车之后几秒进程就没了,logs目录里的启动日志出现UnsupportedClassVersionError或者一串反射相关的IllegalAccessException。

原因:JDK 主版本和安装包编译时使用的版本不匹配。高版本 JDK 在模块访问上有额外限制,中间件的一些反射调用会被拦下来,而且不一定在启动阶段暴露。

解决:先把 JDK 切到 8 或 11 验证功能是否正常,确认可用之后再评估要不要升。切换方式我一般用环境变量临时指定,不动全局JAVA_HOME:

# 临时指定 JDK 11 启动,方便对比 export JAVA_HOME=/usr/lib/jvm/java-11-openjdk export PATH=$JAVA_HOME/bin:$PATH bin/mycat console

如果切了版本就好了,问题定性完成,别再往配置上找。

5.2 服务起来了,但连后端报认证失败

现象:8066 端口能连上,一执行查询就报连接后端失败,日志里能看到认证相关的关键字,但错误信息很含糊。

原因:后端 MySQL 8 默认使用caching_sha2_password,中间件使用的驱动版本对这套认证流程支持不完整。

解决:把中间件专用账号切成mysql_native_password(见第 2 章的ALTER USER),只影响这一个账号。顺带检查useSSL参数,内网环境通常关掉;不关的话握手阶段多一轮协商,某些组合下也会失败。

5.3 用户能登录,但show databases是空的

现象:mysql -P8066 -uapp_user -p登录成功,执行show databases只看到系统库,业务逻辑库不见了。

原因:user.json里的schemas写的是后端物理库名,或者逻辑库配置文件根本没被加载。还有一种情况是文件名和内部name字段不一致,加载时被跳过了。

解决:三步核对——逻辑库 json 里的name、user.json里schemas数组的值、应用连接时use的库名,三者必须完全一致(大小写也要一致)。核完还是空的,去看启动日志里有没有该 schema 的加载记录;没有记录就是文件没被扫到,检查文件后缀和目录层级。

5.4 单条查询很快,列表页却慢到超时

现象:按 ID 查毫秒级返回,后台列表页带个状态筛选就几十秒。

原因:查询条件里没有分片键,中间件把 SQL 广播到所有分片,再把结果拉回来归并排序。分片数越多越慢,而且这是设计使然,不是配置能调优掉的。

解决:从 SQL 侧改。列表页尽量把分片键带进条件;带不进去的,考虑建一张按业务维度重新分片的冗余表,或者把这类查询挪到独立的检索存储上。中间件不是搜索引擎,别指望它把全路由变成索引扫描。改之前先EXPLAIN确认是不是真的全路由,别把慢查询的锅甩错地方。

5.5 改了配置不生效,重启后反而更糟

现象:改完 schema 里的分片数,查询结果和改之前一模一样;顺手重启一次,某些表直接查不到了。

原因:一部分配置支持热加载,一部分只认启动时读到的值;更麻烦的是改配置时把物理分片数改大了,但后端库还没建对应数量的表。

解决:养成「改配置 → 先确认物理侧就绪 → 再重启 → 立即 EXPLAIN 验证」的固定动作。物理侧没准备好就重启,等于把中间件的元数据指向了一批不存在的表。我现在的习惯是每次改分片相关配置之前,先把两个库的表结构、数据分布、数据源列表写在纸上对一遍,再动手。

6. 进阶:把读写分离接上,并用压测确认没白折腾

单库拆完只是第一步,读多写少的业务还得把从库用起来。做法是在 schema 的targetNames里给每个分片配一组主从数据源,主库readOnly设为false,从库设为true,中间件按语句类型分流。

{ "name": "order_logic", "defaultTargetName": "ds0", "replicaDatasources": ["ds0_slave", "ds1_slave"], "shardingTables": { "orders": { "function": "mod_hash", "columns": ["order_id"], "tableCount": 2, "targetNames": ["ds0", "ds1"] } } }

从库的 datasource 配置和主库一样,只是url指向从库、readOnly改成true。这里有个容易忽略的点:主从延迟。写完立刻查的场景,读请求被分到从库就会读到旧数据,表现是「刚下单查不到订单」。业务侧要么把这类查询强制走主库,要么接受延迟并在交互上做兜底提示,中间件本身不解决一致性问题。

验证不能只看功能通不通,得看数据分布对不对、性能有没有真的改善。我用得最多的是三步对照:

验证项做法期望结果
数据分布两个物理库分别count(*)两库行数接近,无单边倾斜
路由正确性抽样 100 个order_id走EXPLAIN全部单库命中,无全路由
读写分流从库开启慢日志后跑读请求从库有查询记录,主库读请求下降

第一步如果出现明显倾斜,多半是分片键取值分布不均,比如自增 ID 取模本身均匀,但业务上大量请求集中在少数几个 ID 上,那就得换散列函数。第二步是全量回归,改一次分片配置就跑一次,别偷懒。第三步要观察一段时间,因为连接池预热和从库延迟都会影响第一时间的判断。

压测我一般用简单的并发脚本打 8066,而不是直接打后端库——中间件自身的连接池、SQL 解析、结果归并都是开销,绕过它测出来的数字没有参考价值。压测期间盯着logs目录的日志增长速度和后端库的Threads_connected,中间件层的瓶颈往往先体现在连接数上,而不是 CPU。

从那以后我每次上新环境,都会先把「JDK 版本、后端认证插件、用户 schemas 三个值、物理表是否就绪」这四项写在便签上逐条打勾,再敲启动命令——这四项里任何一项没对齐,后面都会以完全不相干的形式报出来。分片键的选择我也不会在开会时拍板,而是拉一周的真实慢查询日志统计条件分布,再决定按哪列切。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进