简介:本资源是一套面向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 分片有三个硬约束,违反任一即失效:
父表必须是全局表(
"type": "global")或与子表同分片规则
→ 若user表也按id取模分片,则user_id和user.id的分片结果可能不一致,JOIN 仍跨库。子表
shardingColumn值必须存在于父表主键中
→order.user_id必须是user.id的有效值,否则路由错乱。MyCat 不校验外键,靠业务保证。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修复,再重建全局序列起始值; - 预防:所有分片表必须显式声明主键生成策略,禁止依赖 MySQL
AUTO_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;返回结果关键字段解读:
| 字段 | 含义 | 诊断价值 |
|---|---|---|
sqlId | MyCat 内部唯一 ID | 关联日志、追踪全链路 |
sql | 实际执行的原始 SQL | 确认是否被重写(如SELECT * FROM order可能变成SELECT * FROM order_001) |
executeStatus | success/fail/timeout | 判断是否执行失败 |
routeTarget | dn1[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_logexecuteTime=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。
穿透过程:
show @@sql.execute发现routeTarget为broadcast,且backendTime均 < 50ms → 问题在广播;- 检查
refund表配置,发现shardingColumn设为id,但查询条件是order_id→分片键与查询条件不匹配; - 修改
schema.json:"shardingColumn": "order_id",并重建分片映射; - 重启 MyCat,
show @@sql.execute显示routeTarget: dn1[SELECT * FROM refund_001 WHERE order_id=?]; - 耗时降至 120ms(
backendTime=90ms,executeTime=120ms,差值为网络传输)。
我的习惯是:每次上线新分片表,必用
show @@sql.execute跑 3 条典型 SQL(单条件查询、JOIN、聚合),截图存档。这比写文档管用——当半年后新人问“为什么这个查询慢”,我直接甩出当时的routeTarget截图:“你看,当时就广播了,现在修好了”。希望帮到你。
本文还有配套的精品资源,点击获取