☰
MyCat 2.x 生产级分库分表实战:从零配置到故障定位
2026/10/13 5:37:08 网站建设 项目流程

简介:本资源是一套面向Java后端开发者与数据库架构师的Mycat分布式数据库中间件系统化教程,聚焦从零基础入门到生产级精通,重点解决高并发、大数据量场景下的分库分表、读写分离与高可用架构难题。压缩包共2个文件(1个HTML文档+1个TXT说明),总大小仅6KB,轻量便携,HTML文件承载完整教程正文,涵盖Mycat核心原理、Server/DataNode/Schema/Rule等关键概念、server.xml与schema.xml配置详解、哈希/范围分片策略对比、XA事务处理机制、多节点高可用部署及性能调优要点;TXT文件提供配套学习指引与延伸资源索引。内容结构清晰,理论结合电商、社交等典型业务场景,同步对比ShardingSphere、Cobar等主流中间件,助力读者建立选型判断力与落地实施能力。目前已有453人学习下载,适合希望快速掌握分布式数据库中间件实战能力的中高级开发人员。

1. MyCat 是什么:不是数据库,也不是中间件“玩具”,而是生产级分库分表路由中枢

MyCat 入门到精通教程——这个标题背后藏着一个被严重低估的现实:大量团队在业务增长到单库瓶颈时,第一反应是换 PostgreSQL、上 TiDB、堆读写分离,却忽略了 MyCat 这个已在金融、电商、政务类系统中稳定运行超八年、支撑日均亿级事务的开源数据库中间层。它不替代 MySQL,也不伪装成 NewSQL;它的核心价值,是用 SQL 兼容性换分片自由度:你写的还是SELECT * FROM order WHERE user_id = ?,MyCat 在底层自动路由到order_001表所在的物理库,甚至跨机房做读写分离与故障转移。这不是“代理层”,而是带 SQL 解析、执行计划重写、分布式事务协调(XA/Seata 集成)、全局序列、ER 分片、热数据隔离能力的数据库流量调度中心。适合谁?适合已有成熟 MySQL 架构、不愿重写 DAO 层、又急需横向扩展的中大型项目;不适合纯新项目(可直接选 TiDB 或 Vitess),也不适合单表百万以下的小系统(加索引+缓存更轻量)。本教程不讲“安装就完事”的玩具式操作,而是从真实压测翻车现场出发,带你把 MyCat 跑进生产环境——包括为什么schema.xml里<table>的dataNode顺序会影响 JOIN 性能,为什么server.xml中processors值设为 CPU 核数×2 反而拖慢吞吐,以及最关键的:如何用mycat.log和show @@sql.execute定位一条 SQL 是被广播了、路由错了、还是卡在连接池。


2. 从零启动:用最小配置跑通分片查询与写入闭环

2.1 下载、解压与基础目录结构认知

MyCat 官方已停止维护旧版 1.x,当前主力版本为MyCat 2.x(注意:不是 MyCat 2.0,而是持续迭代的 2.x 系列,最新稳定版为 2.3.0)。它不再依赖 JDK 8,最低要求 JDK 11,且彻底重构了网络层与执行引擎。下载地址统一为 GitHub Release 页面(搜索MyCat-Server官方组织),避免使用镜像站或第三方打包版——曾有团队因某镜像站混入含 Log4j 漏洞的旧包导致线上审计失败。

# 推荐方式:用 wget 直接拉取官方 Release(以 2.3.0 为例) wget https://github.com/MyCatApache/MyCat2/releases/download/2.3.0/Mycat2-2.3.0-linux.tar.gz tar -xzf Mycat2-2.3.0-linux.tar.gz cd mycat2

解压后关键目录说明(非默认路径需在conf/mycat.properties中显式指定):

目录作用是否可改
conf/所有核心配置:mycat.properties,schema.json,router.json,datasource.json✅ 可通过-Dmycat.conf.dir=指定
logs/日志输出,默认按天滚动,mycat.log是主诊断日志✅ 可配logback-spring.xml
lib/内置 JDBC 驱动(MySQL 8.0+)、Netty、Fastjson 等依赖❌ 不建议手动替换
bin/启动脚本:start.sh(前台)、startup.sh(后台)、stop.sh✅ 可修改 JVM 参数

提示:MyCat 2.x废弃了 MyCat 1.x 的schema.xml+rule.xml+server.xml三文件模式,全部合并为 JSON 结构化配置,语义更清晰,但迁移需重写。别试图复制旧版配置——会直接启动失败。

2.2 三步完成最小可用分片环境:建库、配节点、跑 SQL

我们以最典型的「用户订单」场景为例:user表按id分片到 2 个 MySQL 实例,order表按user_id分片并绑定user(ER 分片),确保JOIN user ON order.user_id = user.id不跨库。

第一步:准备两个物理 MySQL 实例(假设 IP 为 192.168.1.10:3306 和 192.168.1.11:3306)

-- 在两台 MySQL 上分别执行(库名必须一致,MyCat 会自动创建同名逻辑库) CREATE DATABASE IF NOT EXISTS mycat_demo; USE mycat_demo; CREATE TABLE user ( id BIGINT PRIMARY KEY, name VARCHAR(64), city VARCHAR(32) ); CREATE TABLE `order` ( id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, amount DECIMAL(10,2), create_time DATETIME );

第二步:配置conf/datasource.json—— 定义物理数据源

{ "dataSources": [ { "name": "dn1", "url": "jdbc:mysql://192.168.1.10:3306/mycat_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "username": "root", "password": "123456", "maxPoolSize": 200, "minPoolSize": 20, "connectionTimeout": 30000 }, { "name": "dn2", "url": "jdbc:mysql://192.168.1.11:3306/mycat_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "username": "root", "password": "123456", "maxPoolSize": 200, "minPoolSize": 20, "connectionTimeout": 30000 } ] }

参数说明:maxPoolSize不宜设过高(超过 300 易触发 MySQLmax_connections限制);connectionTimeout必须小于 MySQL 的wait_timeout(默认 28800 秒),否则空闲连接被 MySQL 主动断开后 MyCat 不感知,下次复用时报Connection closed。

第三步:配置conf/schema.json—— 定义逻辑库、分片规则与 ER 关系

{ "schemas": [ { "name": "TESTDB", "tables": [ { "name": "user", "type": "global", // 全局表,所有节点都存完整副本(适合字典表) "shardingNodes": ["dn1", "dn2"] }, { "name": "order", "shardingNodes": ["dn1", "dn2"], "shardingRule": { "shardingColumn": "user_id", "shardingAlgorithm": "mod-long" } } ], "shardingRules": [ { "name": "mod-long", "type": "hash", "props": { "count": "2" } } ] } ] }

逻辑说明:这里定义了一个名为TESTDB的逻辑库,其中user表设为global类型(全节点复制),order表按user_id字段取模分片到dn1和dn2。mod-long算法表示对user_id做user_id % 2,结果为 0 路由到dn1,为 1 路由到dn2。注意:shardingNodes数组顺序必须与datasource.json中dataSources的声明顺序严格一致,否则分片错位!

第四步:启动并验证

# 启动(首次启动会自动生成 conf/router.json 等文件) ./bin/startup.sh # 检查进程与端口(默认监听 8066,管理端口 9066) ps aux | grep mycat netstat -tuln | grep :8066 # 用 MySQL 客户端连接 MyCat(不是连物理 MySQL!) mysql -h127.0.0.1 -P8066 -uroot -p123456 -DTESTDB

此时执行:

INSERT INTO user(id, name, city) VALUES(1, 'Alice', 'Beijing'); INSERT INTO `order`(id, user_id, amount, create_time) VALUES(1001, 1, 99.9, NOW()); SELECT u.name, o.amount FROM user u JOIN `order` o ON u.id = o.user_id;

若返回Alice, 99.9,说明分片路由、ER 关联、JOIN 下推全部生效——最小闭环跑通。


3. 分片策略深度解析:何时用取模、何时用范围、ER 分片怎么不翻车

3.1 三种主流分片算法的本质与适用边界

MyCat 2.x 内置算法远不止mod-long,其选择直接决定扩容成本与查询性能。我们对比三种高频场景:

算法类型配置示例适用场景扩容代价查询风险
取模(Hash)"count": "4"用户 ID、订单号等离散均匀 ID高:需迁移 50% 数据WHERE id IN (1,2,3)可下推;BETWEEN无法下推,变广播
范围(Range)"mapFile": "range-long-partition.txt"时间字段(如create_time)、地区编码低:新增分片节点即可WHERE create_time > '2023-01-01'可精准路由;LIKE '2023%'无法下推
一致性哈希(Consistent Hash)"virtualNodeCount": "160"需频繁扩缩容的缓存型业务极低:仅移动少量数据IN、BETWEEN均无法下推,强制广播

关键认知:没有“最好”的算法,只有“最不坏”的妥协。比如电商订单表,若按order_id(雪花 ID)取模,看似均匀,但因 ID 生成有时间局部性,会导致新订单集中写入单个节点,形成热点。此时应改用user_id % 4+order_id作为复合分片键,或直接切时间范围。

3.2 ER 分片:让 JOIN 不跨库的核心机制与致命陷阱

ER(Entity Relationship)分片是 MyCat 区别于普通代理的核心能力:它保证强关联的父子表(如user←order)的分片键值落在同一物理节点,使JOIN、IN子查询、GROUP BY能下推执行,而非全节点广播。

配置schema.json中order表的shardingRule需显式声明erTable:

{ "name": "order", "shardingNodes": ["dn1", "dn2"], "shardingRule": { "shardingColumn": "user_id", "shardingAlgorithm": "mod-long", "erTable": "user" // 关键!声明父表名 } }

但 ER 分片有三个硬约束,违反任一即失效:

  1. 父表必须是全局表("type": "global")或与子表同分片规则
    → 若user表也按id取模分片,则user_id和user.id的分片结果可能不一致,JOIN 仍跨库。

  2. 子表shardingColumn值必须存在于父表主键中
    →order.user_id必须是user.id的有效值,否则路由错乱。MyCat 不校验外键,靠业务保证。

  3. INSERT 时必须同时提供父表分片键
    → 插入order时,若只写INSERT INTO order VALUES(1001, 1, ...),MyCat 能路由;但若写INSERT INTO order(user_id, amount) SELECT id, 99.9 FROM user WHERE city='Beijing',则因子查询未显式提供user_id值,MyCat 无法确定目标节点,报错Can't find sharding node for table order。

血泪经验:某次大促前压测发现JOIN查询 QPS 暴跌 70%,排查发现user表被误配为sharding类型(非 global),导致所有order JOIN user变成全节点广播。回滚配置后恢复——ER 分片失效的第一迹象,永远是慢查询日志里出现broadcast字样。

3.3 全局序列:解决分布式主键冲突的工业级方案

MyCat 自带两种全局序列生成器:本地文件(localfile)和数据库(dbseq)。生产环境必须用dbseq,否则多 MyCat 实例间无法同步序列值。

配置conf/sequence.json:

{ "sequences": [ { "name": "GLOBAL_SEQ", "type": "dbseq", "props": { "dataSource": "dn1", // 必须指向一个物理数据源 "tableName": "MYCAT_SEQUENCE", "seqColumn": "seq_value", "increment": "100", "minValue": "1" } } ] }

并在物理 MySQL 的mycat_demo库中建表:

CREATE TABLE MYCAT_SEQUENCE ( name VARCHAR(64) PRIMARY KEY, seq_value BIGINT NOT NULL, increment INT NOT NULL DEFAULT 100, minValue BIGINT NOT NULL DEFAULT 1 ); INSERT INTO MYCAT_SEQUENCE VALUES('GLOBAL_SEQ', 1, 100, 1);

使用时,在schema.json的表定义中添加:

{ "name": "order", "autoIncrement": true, "sequence": "GLOBAL_SEQ", ... }

参数说明:increment=100表示每次从 DB 取 100 个号段缓存在 MyCat 内存中,降低 DB 压力。若单实例 QPS 超 500,建议调至1000;但若 MyCat 频繁重启,小 increment 会导致号段浪费。不要用 UUID 或雪花 ID 替代——MyCat 的INSERT ... SELECT LAST_INSERT_ID()语法依赖连续整数主键。


4. 生产避坑指南:5 个让团队加班到凌晨的真实问题与解法

4.1 现象:SELECT COUNT(*) FROM order执行超时,mycat.log报timeout

原因:MyCat 对聚合函数(COUNT,SUM,AVG)默认走merge模式:先向所有分片发SELECT COUNT(*),再在 MyCat 内存中汇总。若某分片因锁表、慢查询阻塞,整个COUNT就卡住。
解决:

  • 方案 A(推荐):改用SELECT COUNT(*) FROM order WHERE 1=1 /*+ shard(all) */强制广播,避免单点阻塞;
  • 方案 B:在schema.json中为order表添加"mergeSharding": false,关闭自动合并,改由应用层并发查各分片;
  • 方案 C:业务层改用近似统计(如采样SELECT COUNT(*) FROM order TABLESAMPLE SYSTEM (1))。

4.2 现象:INSERT INTO order VALUES(...)成功,但SELECT * FROM order WHERE id=1001查不到

原因:order表未配置autoIncrement:true,且插入时未显式指定id,MyCat 用0作为默认值,导致所有id=0的记录被路由到同一分片(0 % 2 = 0→dn1),而其他分片无数据。
解决:

  • 立即检查schema.json中该表是否漏配"autoIncrement": true;
  • 若已上线,用UPDATE order SET id = LAST_INSERT_ID() + 1 WHERE id = 0修复,再重建全局序列起始值;
  • 预防:所有分片表必须显式声明主键生成策略,禁止依赖 MySQLAUTO_INCREMENT。

4.3 现象:SHOW PROCESSLIST在 MyCat 端看到大量Sleep连接,netstat显示 ESTABLISHED 连接数飙升

原因:应用未正确关闭Connection,MyCat 连接池中的连接被长期占用,最终耗尽maxPoolSize,新请求排队等待。
解决:

  • 在conf/mycat.properties中开启连接泄漏检测:leakDetectionThreshold=60000(毫秒);
  • 配合logback-spring.xml开启com.alibaba.druid.pool.DruidDataSource的warn日志,定位泄漏代码;
  • 强制规范:所有 JDBC 操作必须用try-with-resources或显式conn.close()。

4.4 现象:UPDATE order SET amount=199.9 WHERE user_id=1 AND id=1001影响行数为 0

原因:UPDATE语句中WHERE条件含分片键(user_id)和非分片键(id),MyCat 默认只按分片键路由,id=1001的过滤在分片内执行。若user_id=1的数据实际在dn2,但该语句被错误路由到dn1,则找不到记录。
解决:

  • 在schema.json中为order表添加"shardingKeyCheck": true,开启分片键强校验;
  • 或改写 SQL:UPDATE order SET amount=199.9 WHERE user_id=1 AND id=1001 /*+ shard(dn2) */显式指定节点;
  • 根本解法:UPDATE/DELETE必须带分片键,且避免混合条件。

4.5 现象:MyCat 启动后SELECT @@version返回5.6.29-mycat-2.3.0-release,但连接物理 MySQL 显示8.0.33

原因:MyCat 声称兼容 MySQL 协议,但部分高级特性(如JSON_EXTRACT,WINDOW FUNCTION)未完全实现,应用若用了这些语法,MyCat 解析失败,降级为全节点广播或报错。
解决:

  • 在conf/mycat.properties中设置sqlMode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,关闭 MyCat 的宽松模式;
  • 使用EXPLAIN命令检查 SQL 是否被下推:EXPLAIN SELECT JSON_EXTRACT(data, '$.price') FROM order若返回type: broadcast,则该 SQL 不支持;
  • 落地原则:MyCat 2.x 仅保证 MySQL 5.7 语法兼容性,8.0+ 新特性需在应用层降级处理。

5. 高阶实战:用show @@sql.execute定位慢 SQL 的 3 层穿透法

MyCat 最强大的诊断能力,藏在它的管理端口9066和命令show @@sql.execute中。它不像EXPLAIN那样只告诉你“会怎么执行”,而是直接展示“刚刚执行过的每条 SQL,到底去了哪、干了啥、花了多久”。这是比mycat.log更精准的慢 SQL 定位工具。

5.1 连接管理端口并获取实时执行快照

# 连接 MyCat 管理端(密码默认为 mycat,可在 conf/mycat.properties 中改) mysql -h127.0.0.1 -P9066 -umycat -pmycat # 获取最近 100 条执行记录(按耗时倒序) show @@sql.execute limit 100; # 获取特定 SQL 的详细执行链路(用 sqlId,如 12345) show @@sql.execute where id=12345;

返回结果关键字段解读:

字段含义诊断价值
sqlIdMyCat 内部唯一 ID关联日志、追踪全链路
sql实际执行的原始 SQL确认是否被重写(如SELECT * FROM order可能变成SELECT * FROM order_001)
executeStatussuccess/fail/timeout判断是否执行失败
routeTargetdn1[SELECT * FROM order_001]核心!看是否路由到预期节点,是否被拆分
executeTime毫秒级耗时定位慢点
backendTime后端 MySQL 执行耗时若此值高,问题在物理库;若低而executeTime高,问题在 MyCat 网络或解析

5.2 三层穿透法:从现象到根因的标准化排查流程

第一层:看routeTarget—— 路由是否正确?

  • 正常:dn1[SELECT * FROM order_001 WHERE user_id=1]
  • 异常:broadcast[SELECT * FROM order WHERE user_id IN (1,2,3)]→ 检查分片算法是否支持IN(取模不支持,需改范围或加 hint)
  • 异常:dn1[SELECT * FROM order_001], dn2[SELECT * FROM order_002]→JOIN未下推,检查 ER 分片配置

第二层:比executeTime与backendTime—— 瓶颈在哪?

  • executeTime=1200ms,backendTime=1150ms→ 物理库慢,查slow_query_log
  • executeTime=1200ms,backendTime=50ms→ MyCat 解析/合并慢,检查mycat.log中是否有parse error或merge timeout

第三层:查sqlId关联日志 —— 深挖上下文
在logs/mycat.log中搜索sqlId=12345,你会看到:

2024-06-15 10:23:45 [INFO] [ShardingRouter] Route to dn1 with sql: SELECT * FROM order_001 WHERE user_id=1 2024-06-15 10:23:45 [DEBUG] [ExecuteHandler] Execute on backend connection: com.mysql.cj.jdbc.ConnectionImpl@abc123 2024-06-15 10:23:46 [WARN] [MergeHandler] Merge result timeout, cancel all backend tasks

→ 第三行MergeHandler超时,说明SELECT返回数据量过大(如SELECT * FROM order无LIMIT),MyCat 内存合并超时。解决方案:加LIMIT,或调大conf/mycat.properties中的mergeTimeout=30000。

5.3 一个真实案例:某支付系统退款查询从 8s 降到 120ms

场景:SELECT * FROM refund WHERE order_id=? AND status='SUCCESS'平均耗时 8s。
穿透过程:

  1. show @@sql.execute发现routeTarget为broadcast,且backendTime均 < 50ms → 问题在广播;
  2. 检查refund表配置,发现shardingColumn设为id,但查询条件是order_id→分片键与查询条件不匹配;
  3. 修改schema.json:"shardingColumn": "order_id",并重建分片映射;
  4. 重启 MyCat,show @@sql.execute显示routeTarget: dn1[SELECT * FROM refund_001 WHERE order_id=?];
  5. 耗时降至 120ms(backendTime=90ms,executeTime=120ms,差值为网络传输)。

我的习惯是:每次上线新分片表,必用show @@sql.execute跑 3 条典型 SQL(单条件查询、JOIN、聚合),截图存档。这比写文档管用——当半年后新人问“为什么这个查询慢”,我直接甩出当时的routeTarget截图:“你看,当时就广播了,现在修好了”。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询