MyCAT按月分片实战:ZIP包部署、路由原理与跨年避坑

发布时间:2026/9/25 8:14:11
MyCAT按月分片实战:ZIP包部署、路由原理与跨年避坑 简介基于MyCat 1.6.7.6正式版源码深度定制的按月分表增强包面向使用MyCat做数据分片、且业务数据随时间持续增长的开发与运维人员。通过subTablestableName_$202101-?与subTableWayBYMONTH组合配置前半段指定起始月份后缀?代表当前月份系统可按当前月份动态匹配子表配合建表脚本可实现子表自动增长解决固定分表规则难以应对跨月数据增长的问题。压缩包共111个文件大小约24.86MB。其中53个jar为改版核心及运行依赖18个properties用于连接池与参数调节10个xml定义了分片规则、schema和server配置并附有shell启停脚本、SQL初始化脚本与备份配置便于直接部署或对照二次开发。已有374人学习下载。借助其中可动态建表的分表思路与配置示例可快速落地月度分表方案减少自研中间件的排错成本。适合熟悉MyCat基本使用、希望扩展按月分表能力的中高级后端工程师。1. mycat-1.6.7.6_BYMONTH.zip先搞清楚这个 ZIP 包里的 BYMONTH 在解决什么看到 mycat-1.6.7.6_BYMONTH.zip 这个文件名很容易先入为主地把它当成一个“带按月分片插件”的普通压缩包。它确实是 MyCAT 1.6.7.6 的发布包BYMONTH 对应的是按月分片的方案主要用来处理订单、账单、操作日志这类随时间线性增长、查询也基本带着时间范围的流水表。但真正落地时有一个反直觉的起点MyCAT 并不会因为你 SQL 里写了 create_time 就自动把数据拆到对应月份的物理表。BYMONTH 要生效需要 schema.xml 里的逻辑表映射、rule.xml 里的路由函数、应用端 SQL 是否带分片键三者同时成立。适合谁MySQL 单表已经几千万行、想低成本做水平拆分、又不愿意把分库分表写进业务代码的团队。2. 读懂 BYMONTH 分片MyCAT 是怎么把一条带日期的 SQL 送进 12 个月表的ZIP 包里虽然带了 bin、conf、lib 这些目录但 BYMONTH 本身不是一个独立进程而是 conf 目录里一组 XML 配置的组合。MyCAT 启动后是一个 MySQL 协议兼容的代理应用连上 8066 端口SQL 进去之后由 MyCAT 解析逻辑表、匹配路由函数、计算 dataNode 下标再把改写后的 SQL 发到真实 MySQL 库。搞清楚这条链路后面的配置才不会变成“改完不知道管没管用”。2.1 解压后先看目录bin、conf、lib 里藏着路由三件套MyCAT 的发布包结构非常稳定解压后一般会看到 bin、conf、lib 三个目录logs 目录会在首次启动后生成。bin 下面是 Linux 的 mycat 启动脚本和 Windows 的 startup.batconf 目录是全部静态配置lib 是运行依赖的 jar 包。真正决定 BYMONTH 行为的是 conf 下的三个文件schema.xml 定义“逻辑表到 dataNode 再到 MySQL 库”的映射rule.xml 定义路由函数和参数server.xml 定义逻辑库名、端口和账号。每次从 ZIP 包部署第一步不是着急 start而是先把这三个文件读一遍并保留一份原始备份。这里有一个常见误区以为解压完 ZIP 包就等于装好了中间件。实际上 MyCAT 只是个转发层它不会帮你创建 MySQL 库也不会帮你建表更不负责同步分片规则到真实实例。你从 ZIP 包拿到的是一套“路由引擎”而路由规则需要你自己按业务写进 XML。所以第一次接触这个包的人建议按“先理解 schema再理解 rule最后调 server”的顺序去读配置而不是直接改端口。2.2 partitionByMonth 的求值逻辑从月份差到 dataNode 下标MyCAT 里对应 BYMONTH 的类一般是 io.mycat.route.function.PartitionByMonth。它的输入是分片键的值输出是一个整数下标用来决定 SQL 发给哪个 dataNode。计算分三步先用 dateFormat 把字符串解析成日期然后算出这个日期与 sBeginDate 之间的月份差最后对分片节点数取模落到 0 到 N-1 的下标。例如 sBeginDate 配成 2023-01-01分片键传 2023-06-15月份差是 5如果一共有 12 个节点index 就是 5对应 dn5物理库就是 db_202306。下面这张表可以直观看到跨年时的路由变化create_timemonthDiffindexdataNode物理库2023-01-1500dn0db_2023012023-06-1555dn5db_2023062023-12-311111dn11db_2023122024-01-01120dn0db_202301最后一行就是这个规则最典型的坑2024 年 1 月的数据会路由到 db_202301而不是新的 db_202401。很多团队跨年后发现“一月的账全写到去年库里”就是这么来的。原因不是 MyCAT 算错了而是它按月份差取模跨年后差值变成 12取模归零。理解了这个逻辑才知道什么时候该滚动配置什么时候可以接受循环。dateFormat 的作用是字符串到日期的唯一依据。MyCAT 不会看 MySQL 字段类型它拿到的就是 SQL 里的字符串。只要应用传的格式和 dateFormat 不一致例如 dateFormat 是 yyyy-MM-dd应用传的是 yyyy-MM-dd HH:mm:ss解析就会失败轻则抛异常重则 SQL 被广播到全部分片。这个点后面避坑章节会重点展开。2.3 分片键选型为什么 create_time 比主键更适合 BYMONTH按月分片的分片键一般选业务查询最频繁的时间字段而不是主键。按主键 id 分片的问题在于查“最近三个月订单”时MyCAT 不知道 id 和时间的关系只能把 SQL 发到全部 12 个节点每个节点都做一次全表扫描再合并结果。按 create_time 分片则完全不同where 条件里带上时间范围后路由能精确定位到 1 到 3 个月分片IO 和 MySQL 压力都小很多。代价是不带 create_time 的 SQL 无法定位分片MyCAT 只能广播。这类语句要尽量避免或者应用层强制改造成带上时间范围。另一个容易忽略的点是MyCAT 不负责生成 create_time也不认数据库字段的 default 值。插入时应用必须显式传入分片键否则路由阶段就拿不到值数据要么报错要么被丢到默认节点。选 BYMONTH 前先确认所有写入路径都能保证这个字段非空不然后面查数会查出“数据失踪”的诡异问题。3. 从 ZIP 解压到跑通最小按月分片部署与配置文件怎么写下面用一套最小可运行配置走一遍从 ZIP 解压到 MyCAT 启动、再到 SQL 路由到 db_202306 的完整流程。这里采用最常见也是最容易理解的分库方案建 12 个 MySQL 库每个库一张同名的 orders 表MyCAT 按月份把数据路由到对应库。3.1 解压与运行环境准备Linux 下用 unzipWindows 下用 7-Zip先把 ZIP 包放到目标机器确保已经装了 JDK 8然后用 unzip 解压。Linux 下一般这样操作mkdir -p /opt/mycat unzip mycat-1.6.7.6_BYMONTH.zip -d /opt/mycat cd /opt/mycat/bin ./mycat start tail -f ../logs/mycat.logunzip 的 -d 参数指定解压目标目录这里解压到 /opt/mycat。如果系统提示 unzip 命令不存在先安装 unzip。启动脚本会读取 MYCAT_HOME 或者根据脚本位置推导目录所以尽量把 ZIP 包解压到一个没有空格、没有中文的纯路径下避免 Windows 解压后常见的路径问题。如果 Linux 下解压时报“需要密码”或者 CRC 错误先不要急着重下有可能是伪加密标志导致这个放在避坑章节说。Windows 上常见做法是用 7-Zip 打开 ZIP 包解压到 D:\mycat 这类目录然后编辑 conf 下的配置最后双击 bin\startup.bat 启动。启动后去看 logs 目录下的 mycat.log看到 “MyCAT Server startup successfully” 才说明起来。不要只看窗口还在就以为成功MyCAT 是 Java 进程启动失败时窗口可能会一闪而过。3.2 最小 schema.xml一个逻辑表对应 12 个按月 dataNode分库方案下dataNode 的 database 是真实 MySQL 库名逻辑表名 orders 在每个库里保持一致。schema.xml 的关键配置如下?xml version1.0 encodingUTF-8? !DOCTYPE mycat:schema SYSTEM schema.dtd mycat:schema xmlns:mycathttp://io.mycat/ schema nametestdb checkSQLschemafalse sqlMaxLimit100 table nameorders primaryKeyid dataNodedn$0-11 rulesharding-by-month / /schema dataNode namedn0 dataHosthost1 databasedb_202301 / dataNode namedn1 dataHosthost1 databasedb_202302 / dataNode namedn2 dataHosthost1 databasedb_202303 / dataNode namedn3 dataHosthost1 databasedb_202304 / dataNode namedn4 dataHosthost1 databasedb_202305 / dataNode namedn5 dataHosthost1 databasedb_202306 / dataNode namedn6 dataHosthost1 databasedb_202307 / dataNode namedn7 dataHosthost1 databasedb_202308 / dataNode namedn8 dataHosthost1 databasedb_202309 / dataNode namedn9 dataHosthost1 databasedb_202310 / dataNode namedn10 dataHosthost1 databasedb_202311 / dataNode namedn11 dataHosthost1 databasedb_202312 / dataHost namehost1 maxCon500 minCon10 balance0 writeType0 dbTypemysql dbDriverjdbc writeHost host192.168.1.10 urljdbc:mysql://192.168.1.10:3306?useUnicodetrueamp;characterEncodingutf8 usermycat password123456 /writeHost /dataHost /mycat:schematable 节点里的 dataNodedn$0-11 表示逻辑表 orders 分布到 dn0 到 dn11 这 12 个数据节点rule 指向 rule.xml 里定义的路由规则。primaryKey 只用于主键查询的辅助优化不能替代分片键。dataNode 的 database 属性是真实 MySQL 里的库名必须提前建好。dataHost 的 url 连接到 MySQL 实例注意 XML 里 要写成 否则 DTD 解析直接报错。server.xml 里也要有对应的逻辑库和用户否则客户端连不上?xml version1.0 encodingUTF-8? !DOCTYPE mycat:server SYSTEM server.dtd mycat:server xmlns:mycathttp://io.mycat/ user nameroot property namepassword123456/property property nameschemastestdb/property /user /mycat:server这里 user 的 schemas 必须包含 schema.xml 里定义的 testdb。端口如果不改默认数据端口 8066管理端口 9066。密码这里只是示例生产环境不要用弱口令。3.3 rule.xml 里 BYMONTH 函数的 4 个必调参数rule.xml 由 tableRule 和 function 两部分组成。tableRule 声明哪个列作为分片键function 实现具体路由算法。最小配置如下tableRule namesharding-by-month rule columnscreate_time/columns algorithmpartition-by-month/algorithm /rule /tableRule function namepartition-by-month classio.mycat.route.function.PartitionByMonth property namedateFormatyyyy-MM-dd/property property namesBeginDate2023-01-01/property property namesEndDate2023-12-31/property /function这里 4 个参数要一起看。columns 是分片键字段名必须和表结构里的真实字段完全一致大小写敏感。dateFormat 决定分片键字符串怎么解析成日期。sBeginDate 是月份差计算基准日sEndDate 是循环窗口结束日。上面这个配置的含义是以 2023 年 1 月为起点在 2023 年 12 月这个窗口内按月份落到 0 到 11 号分片一旦日期进入 2024 年月份差超过 11取模后又回到 0 号分片。如果不配 sEndDateMyCAT 也会按 12 个月做模运算区别在于规则可读性不强。建议显式写出 sBeginDate 和 sEndDate至少让后来接手的人一眼看出这是按年循环。还需要注意columns 不能是 select 别名必须是表里的真实列。这个列最好在物理表上有索引否则即使路由到了单个月MySQL 那边仍然会做全表扫描。配置好后在 12 个库里建表。循环执行的脚本长这样for db in db_202301 db_202302 db_202303 db_202304 db_202305 db_202306 \ db_202307 db_202308 db_202309 db_202310 db_202311 db_202312; do mysql -h127.0.0.1 -umycat -p123456 -e CREATE TABLE IF NOT EXISTS \$db\.orders ( id BIGINT NOT NULL, create_time DATETIME NOT NULL, amount DECIMAL(10,2), PRIMARY KEY (id) ) done这个脚本在每台真实 MySQL 实例上都要跑一遍。如果你的 12 个库分布在多台机器dataNode 里要分别引用不同的 dataHost建表时也要分别登录执行。MyCAT 不会自动建表这一步漏了后面 insert 会直接报 Table doesnt exist。全部改完后重启 MyCATcd /opt/mycat/bin ./mycat restart mysql -h127.0.0.1 -P8066 -uroot -p123456 testdb -e explain select * from orders where create_time2023-06-15;看到 explain 结果指向 dn5就说明路由规则已经加载。如果报错先去 logs/mycat.log 看配置加载失败的原因最常见的是 XML 标签写错、dataNode 名字对不上、或者 MySQL 连接串配错。4. MyCAT 1.6.7.6 按月分片避坑ZIP 包与路由现场最常见的 5 个问题这一章我把过去带团队做 MyCAT 分片时最常踩的 5 个坑列出来每一条都按“现象、原因、解决”的顺序说。前两条和 ZIP 包、跨年配置有关后三条是配置正确后依然会翻车的运行时问题。4.1 ZIP 包解压报错伪加密与 unzip 的兼容问题现象Linux 服务器上执行 unzip mycat-1.6.7.6_BYMONTH.zip报了 unable to expand、CRC mismatch或者莫名其妙提示输入密码但是这个 ZIP 包你明明没设过密码。原因很多发行包是在 Windows 上用压缩工具重新打包的压缩工具把 ZIP 的 general purpose bit 加密位置了 1但没有真正加密文件内容这就是 ZIP 伪加密。Linux 自带 unzip 检测到加密位后会误以为文件有密码于是直接拒绝解压或解压出损坏文件。解决先用 7-Zip 在 Windows 上打开确认内容能正常读取然后重新压缩一次或者直接在 Linux 上用 zip 工具修复zip -FF mycat-1.6.7.6_BYMONTH.zip --out mycat_fixed.zip unzip mycat_fixed.zip -d /opt/mycat修复后的包如果能正常列出 bin、conf、lib部署就没问题。如果仍然 CRC 错误再用 sha256 和官方校验值比对确认是不是下载过程损坏。这个坑很隐蔽容易让人误判成包本身不完整实际上只是压缩标志位问题。4.2 跨年路由错乱sBeginDate 和 sEndDate 没对齐现象2024 年 1 月 1 日之后应用插入的订单没有进入新库 db_202401反而全部进入 db_202301查 2024 年 1 月的数据路由结果还是 dn0。原因这是 partitionByMonth 取模逻辑的必然结果。sBeginDate 配的是 2023-01-012024-01-01 的月份差是 1212 对 12 取模等于 0所以路由回 dn0。如果你的业务预期是“每年 1 月都进 db_202301”这个行为反而是对的但如果你希望 2024 年 1 月进 db_202401就必须在跨年时做一次滚动配置。解决常见做法是每年 12 月 31 日低峰期预先建好 db_202401 到 db_202412 这 12 个库然后改 schema.xml 里 dataNode 的 database 名同时把 rule.xml 的 sBeginDate 改成 2024-01-01sEndDate 改成 2024-12-31再执行 reload 或重启。注意只要 sBeginDate 不和业务当前年份对齐跨年后所有统计都会错位而且因为 SQL 能正常返回数据非常难发现。每年至少验证一次 explain 路由是这个方案里最便宜的后悔药。4.3 分片键格式不一致路由不报错但数据跑偏现象应用一直传 create_time 2023-06-01 10:00:00dateFormat 配的是 yyyy-MM-ddMyCAT 日志出现 DateParseException但部分请求依然成功返回数据落在不确定的节点上。原因MyCAT 的路由解析完全依赖 dateFormat 做字符串匹配。带时分秒的字符串在 yyyy-MM-dd 格式下解析失败不同小版本的处理不一样有的抛异常有的退化成默认路由。应用程序员看到“偶发报错”又拿不到全链路日志时通常第一反应是 MySQL 慢查询不会想到是分片键格式问题。解决把 dateFormat 统一成应用实际传参的格式。如果业务传的是 datetime就配成 yyyy-MM-dd HH:mm:ss如果传的是纯日期就配 yyyy-MM-dd。另外用 PreparedStatement 的 setTimestamp 传参时MyCAT 拿到的是 JDBC 驱动格式化后的字符串时间戳格式里可能带毫秒建议在 JDBC 连接串里固定 dateFormat并且压测时用和线上一致的类型。这个坑在联调阶段最容易被忽略因为测试数据量小路由错了也感觉不出来。4.4 建表语句没同步到各分片MyCAT 不会自动建表现象启动后 select 正常insert 报 Table db_202306.orders doesnt exist直接连 MySQL 执行同样的 SQL 又能成功。原因MyCAT 只是路由代理不维护表结构。schema.xml 里定义了 12 个 dataNode但如果只在其中一个 MySQL 库建了表其他 11 个库根本没表MyCAT 把 SQL 路由过去后自然报错。很多第一次用的人以为 MyCAT 会像中间件一样自动建表实际上它对 DDL 也只做转发不会生成物理表。解决在 12 个库中全部执行建表语句脚本在第 3.3 节已经给过。需要注意两个细节一是建表语句要在每台 MySQL 实例上执行不能只在 MyCAT 所在机器执行二是如果后续要新增分片库同样要执行一次建表。更稳妥的做法是把建表语句写进自动化脚本放到发布流程里而不是手动一台台执行。4.5 dataHost 连接池与超时路由对了也卡在 MySQL 层现象并发一上来MyCAT 日志报 Waiting for Connection Timeout或者 connection is closed同一个查询直接连 MySQL 只要几十毫秒走 MyCAT 就超时。原因dataHost 的 maxCon 配得太小连接被长时间占用新请求等不到连接。MySQL 端的 max_connections 也可能不够。还有一个容易被忽略的原因如果写库压力大又把 balance 调成了读写分离模式读请求可能被分到延迟较高的从库导致路由正确但执行慢。解决先把 dataHost 的 maxCon 调到 500 到 1000同步调大 MySQL 的 max_connections。再用管理端口 9066 连接执行 show datasource 查看每个 dataNode 的连接数。如果出现大量连接被占用优先排查是不是有业务把 MyCAT 当成普通连接池反复创建连接连接用完没释放。这是我最常遇到的一种“配置没问题但性能翻车”的场景问题往往在应用端连接管理。5. 不要只测功能用 explain 和滚动配置验收 BYMONTH我验收 BYMONTH 分片一般不走“造数看能不能查到”这条路因为造数很容易掩盖路由问题。先直接用 MyCAT 的 explain 看一眼路由目标再决定要不要放流量。连接 8066 数据端口执行 explainSQL 不需要真跑MyCAT 会直接返回路由信息mysql -h127.0.0.1 -P8066 -uroot -p123456 testdb \ -e explain select * from orders where create_time2023-06-15;正常情况下结果里会看到 dataNode dn5也就是 db_202306。分别查两个库的行数做一个交叉验证mysql -h127.0.0.1 -umycat -p123456 -e select count(*) from db_202306.orders; mysql -h127.0.0.1 -umycat -p123456 -e select count(*) from db_202307.orders;插入一条 2023-06-15 的数据后db_202306 的行数应该增加其他库不变。这个习惯能帮你发现所有和格式、跨年、配置加载相关的问题。跨年滚动配置也有一个值得养成的技巧不要直接改正在运行的 schema.xml 再重启。常见做法是先建好新一年的 12 个库在管理端口 9066 执行 reload 之前把 schema.xml 里的 database 全部切到 db_202401 到 db_202412同时把 rule.xml 的 sBeginDate 改成新年第一天最后执行mysql -h127.0.0.1 -P9066 -uroot -p123456 -e reload config_all;reload 不会踢掉老连接所以一定要低峰期操作并且 reload 后立刻跑一条 explain 确认 2024-01-05 的数据路由到 dn0 所对应的新库。我第一次做跨年滚动时就是忘了改 sBeginDate结果一月份数据全写进旧表第二天看 explain 才发现。后来我养成了习惯每次改规则后先 explain 一条已知日期的 SQL再放流量。这个习惯救了我好几次希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询