简介这是一份面向MySQL中间件使用者的按月分表增强包基于MyCat 1.6.7.6正式版源码二次开发。核心改进是新增subTables的BYMONTH分表方式支持“tableName_$202101-?”这类正则配置从设定月份开始问号自动动态代表当前月份无需手动维护完整表名子表需先在MySQL中真实存在并可借助动态建表实现子表按月自动增长大幅降低按月分区的维护成本对需要保留历史流水、按月归档的业务场景尤其适用。压缩包共111个文件包括53个jar依赖、18个properties配置、14个txt说明文档、10个xml规则文件、4个sh运维脚本、2个sql初始化脚本等整体约24.86MB其中properties和xml用于调整分表与服务规则jar为运行时依赖txt和sh提供改造说明与启停辅助资料组织完整。当前已有375人学习下载。通过该包可快速获得按月分表的可运行实现参考其中的源码改动与正则匹配思路还能进一步拓展到季度、年度等其他时间维度分表场景适合MyCat使用者与源码二次开发人员参考。1. mycat-1.6.7.6_BYMONTH.zip把单表千万数据按月拆开业务 SQL 几乎不用改当一张订单表以每天几十万行的速度增长单表行数冲上千万之后即使索引全部命中磁盘 IO 和行锁竞争也会让延迟肉眼可见地上升。删数据不舍得加机器没预算业务 SQL 又改不动这是大多数团队第一次意识到需要分库分表的时刻。mycat-1.6.7.6_BYMONTH.zip 就是这类场景下可以直接投入的方案它是一个 MySQL 数据库中间件发行包自带按月分片的 PartitionByMonth 算法应用连接 Mycat 就像连接一个普通 MySQL业务 SQL 基本不用动就能把一张逻辑大表按月份拆到多个物理库表。这个方案适合三类人单表数据量已经告警、正在做数据库选型评估的团队想在不改业务代码的前提下做冷热数据分离的运维工程师以及被分配了把订单库拆了这类任务、需要快速找到落地路径的开发者。下面我从分片模型、部署操作、BYMONTH 规则配置、常见坑和验收技巧五个层面展开每一步都可以照着操作。2. 先搞清楚 Mycat 的分片模型三个配置文件和一条数据流先别急着解压和启动把 Mycat 的分片模型理清楚后面改配置时才不会一头雾水。Mycat 本身不是一个存储引擎它不真正保存数据只负责把 SQL 路由到正确的物理 MySQL 上执行然后汇总结果。2.1 应用与 MySQL 之间的中间层Mycat 到底代理了什么Mycat 的本质是一个实现了 MySQL 协议的代理层。后端服务把 jdbc:mysql://mycat_host:8066/LOGIC_DB 当成一个普通 MySQL 实例来连接Mycat 收到 SQL 后做三件事解析 SQL、按分片规则决定去哪个物理节点执行、汇总结果返回给应用。对业务代码来说它连的就是一个大 MySQL这个假象是 Mycat 最核心的价值。代理层解决了两个实际问题。第一是连接收敛后端几十个服务实例不会直接打爆 MySQL 的连接数所有连接都落在 Mycat 的连接池上。第二是透明分片逻辑表名在多个物理库中真实存在Mycat 根据分片字段找到正确的物理表。分片规则越清晰代理层的性能损耗越低规则写得太模糊Mycat 就会把所有分片都查一遍再合并结果这种广播是最需要避免的。理解这个代理模型还有一个实际意义你的 SQL 最终会被真实执行在某个物理节点上所以物理库的容量、慢查询、死锁这些指标最终要回到 MySQL 侧去看不能只盯着 Mycat 的监控页面。2.2 schema.xml、rule.xml、server.xml 各管一段谁也替代不了谁Mycat 的配置集中在 conf 目录下的三个 XML 文件里。我见过不少团队只改其中一个却指望它生效路由不对就开始怀疑中间件有 bug实际上这三份文件的分工非常明确。server.xml 管 Mycat 自己的连接账号、端口、系统参数。用什么用户名密码连接逻辑库允许的最大连接数全局序列采用哪种方案都在这里定义。schema.xml 管逻辑库和物理库的映射逻辑库里有几张逻辑表、每张表对应哪几个数据节点、每个数据节点指向哪台 MySQL 实例、读写是否分离。rule.xml 管分片规则哪张表按哪个字段分片、用什么算法、算法参数怎么配。三个文件的关系可以这么记schema.xml 决定有几张表、表在哪rule.xml 决定数据去哪张物理表server.xml 决定谁能连进来、连接资源怎么受限。改完任何一份都要重启 Mycat 才生效因为它们都是在启动时一次性加载的。我一般在改动 schema.xml 和 rule.xml 后会先启动一个 console 模式的临时实例验证配置后再切流量避免把线上实例搞挂。2.3 分片字段的四个硬性条件缺一个后面就等着重构分片字段是整个分片方案里唯一不能拍脑袋决定的配置。它有四个硬性条件。第一分布够均匀。按月分片天然适合订单、日志这类按时间累积的数据但如果是按用户 ID 分片就要考虑高活跃用户是否会让某一个分片过热。第二不允许更新。一旦某行数据的分片字段值被修改它在逻辑上需要被移动到另一个物理分片Mycat 对这种数据移动的支持非常有限生产上基本只能靠应用层处理。第三查询条件必须常带。路由能生效的前提是 SQL 的 where 条件里能提取到分片字段的值如果业务查询经常不带这个字段每次都是全分片广播性能会比单库还差。第四类型稳定。分片字段的类型和传参格式必须长期不变改类型意味着所有历史数据的路由结果全部改变这类重构在分库分表场景里代价极高。这四个条件在选字段阶段就要全部过一遍不要等上线后再回头改。按月分片之所以是大多数团队的第一选择就是因为时间字段天然满足前三条而第四条只要约定好日期格式就能控制住。3. 把 mycat-1.6.7.6 跑起来的完整操作解压、调 JVM、配数据节点这一章从拿到 mycat-1.6.7.6_BYMONTH.zip 开始一步步把它跑起来并把三个真实 MySQL 数据节点配好。整个过程没有需要编译的代码但有一些参数不调好后面会反复出问题。3.1 解压和 JVM 参数调整启动前必须改的两处发行包拿到后先解压到固定目录。Mycat 解压后自带 bin、conf、lib、logs 等目录不需要编译只要有 Java 运行环境就能启动。# 解压发行包到 /opt 目录注意 zip 内自带 mycat 目录 unzip mycat-1.6.7.6_BYMONTH.zip -d /opt/ # 确认目录结构和 Java 环境 ls /opt/mycat/ java -version # 启动前修改 JVM 堆内存默认 256M 在分片场景下撑不住 # 编辑 /opt/mycat/conf/wrapper.conf至少调整为 2G/4G # wrapper.java.initmemory2048 # wrapper.java.maxmemory4096 # 先以 console 模式启动日志实时打到终端方便排错 /opt/mycat/bin/mycat console # 另开一个终端查看进程状态 /opt/mycat/bin/mycat statusmycat 启动脚本支持 start、console、stop、status 等参数。console 模式用于调试退出终端进程就停确认配置无误后生产环境改用后台守护方式启动。wrapper.conf 里的两个堆内存参数分别控制初始堆和最大堆分片节点越多、单条结果集越大这两个值就要给得越足。我见过不少部署表配置完全没问题但因为堆内存默认值太小运行几周后频繁 Full GC路由性能急剧下降误以为是 Mycat 的 bug。这类玄学问题排查顺序永远是先看 JVM 再看配置。3.2 用三个真实 MySQL 实例配置分片数据节点这一步要准备三台可用的 MySQL 实例。它们可以分布在三台物理机上也可以在同一台机器上跑三个不同端口的实例。分片的物理边界越独立后面的水平扩展收益越大如果只是把三个库放在同一台机器上那解决的只是单表锁竞争磁盘 IO 瓶颈还在。?xml version1.0 encodingUTF-8? mycat:schema xmlns:mycathttp://io.mycat/ !-- 逻辑库名应用 JDBC 连接时使用 -- schema nameLOGIC_DB checkSQLschematrue sqlMaxLimit100 !-- order_log 按 order_time 分片三个数据节点 -- table nameorder_log dataNodedn1,dn2,dn3 rulesharding_by_month/ /schema !-- 数据节点每个节点指定物理库名 -- dataNode namedn1 dataHosthostA databaselogdb_01 / dataNode namedn2 dataHosthostB databaselogdb_02 / dataNode namedn3 dataHosthostC databaselogdb_03 / !-- 物理主机 Abalance0 表示先不开启读写分离 -- dataHost namehostA maxCon100 minCon10 balance0 writeType0 dbTypemysql dbDrivernative heartbeatselect user()/heartbeat writeHost hostmysqlA url192.168.10.11:3306 usermcat passwordChangeMe123/ /dataHost !-- hostB 与 hostC 的 dataHost 结构与 hostA 一致按实际 IP 替换 -- dataHost namehostB maxCon100 minCon10 balance0 writeType0 dbTypemysql dbDrivernative heartbeatselect user()/heartbeat writeHost hostmysqlB url192.168.10.12:3306 usermcat passwordChangeMe123/ /dataHost dataHost namehostC maxCon100 minCon10 balance0 writeType0 dbTypemysql dbDrivernative heartbeatselect user()/heartbeat writeHost hostmysqlC url192.168.10.13:3306 usermcat passwordChangeMe123/ /dataHost /mycat:schemaschema 标签里声明了逻辑库 LOGIC_DBtable 标签的 dataNode 属性列出三个数据节点rule 属性指向 rule.xml 里的规则名。dataNode 只是逻辑指针真正连接到哪台 MySQL 由 dataHost 决定。writeHost 里的 url 指向物理 MySQL账号需要有建表、读写和查询元数据的权限。这里 balance0 表示所有读写都走 writeHost先把分片跑通再加 readHost排查问题时少一个变量总是好的。三个物理库名 logdb_01、logdb_02、logdb_03 需要在 MySQL 侧提前建好Mycat 不会自动建库。建表 DDL 也必须到每个物理库手动执行具体内容下一章会讲到。3.3 EXPLAIN 验证分片路由上线前必做的自检配置是否生效不要凭感觉直接查路由。-- 在 Mycat 逻辑库执行观察输出里的节点信息 EXPLAIN SELECT * FROM order_log WHERE order_time 2025-03-12;如果配置正确EXPLAIN 输出的节点信息会指向 dn1、dn2、dn3 中的某一个如果同时出现多个节点说明这条 SQL 的条件没有被识别为分片字段Mycat 走了全分片广播。这条命令是我每次改完 rule.xml 之后必做的第一个检查它比任何日志都直观展示的是 Mycat 内部真实的路由计算结果。广播本身在少数场景下是可以接受的比如按月分片后某些运营报表必须跨全月查询但日常交易类 SQL 必须做到精确路由否则分片不但没解决问题还引入了一层代理开销。4. BYMONTH 分片规则从 rule.xml 到建表语句三处配置逐一攻破这一章进入标题的核心BYMONTH 按月分片的具体配置。很多文章只贴 rule.xml 就结束了但实际落地时物理库的 DDL 和节点数规划同样决定成败。4.1 PartitionByMonth 算法原理月份差取模一句话就能讲透Mycat 自带的按月分片算法是 PartitionByMonth它的内部逻辑可以简化成一句话把传入日期与基准日期的月份差算出来对数据节点总数取模结果就是目标分片下标。具体来说算法会解析日期字符串得到年、月再与 sBeginDate 配置的起始年、月做差值。月份差等于 0 表示当月数据落在第 1 个节点等于 1 落在第 2 个节点依此类推。如果数据节点有 12 个那么一年 12 个月刚好各占一个节点第 13 个月到来时月份差对 12 取模回到 0数据再次写入第 1 个节点循环往复。这个算法的优势是简单、计算开销极小路由只依赖日期字段不需要查任何元数据表。代价是它不感知业务流量的波峰波谷如果业务旺季是 3 月那 3 月对应的那个分片永远是最热的其他分片相对空闲。理解这一点很重要因为它直接决定了下面节点数的配置策略。4.2 按月分片的 tableRule、function 和物理表 DDLrule.xml 是分片规则的唯一落点。配置分两层tableRule 定义这张表的规则入口function 定义实际执行分片的算法和参数。!-- tableRuleorder_log 表按 order_time 字段进行分片 -- tableRule namesharding_by_month rule columnsorder_time/columns algorithmpartbyMonth/algorithm /rule /tableRule !-- function使用 Mycat 自带的 PartitionByMonth -- function namepartbyMonth classio.mycat.route.function.PartitionByMonth property namedateFormatyyyy-MM-dd/property property namesBeginDate2024-01-01/property /functioncolumns 是逻辑表的分片字段必须与 schema.xml 里 table 的列名一致algorithm 的名字要能对上 function 的 name 属性。dateFormat 是日期字符串的解析格式决定了应用层传参能不能被正确识别。sBeginDate 是月份差计算的基准月我强烈建议设置成业务最早可能出现数据的月份而不是部署当天原因在下一章的排坑里会详细说。写好 rule.xml 后还要到三个物理库执行建表语句。Mycat 分片只是路由层物理表和索引必须自己在每个库建好一个都不能少。-- 在 logdb_01、logdb_02、logdb_03 三个物理库分别执行 CREATE TABLE order_log ( id BIGINT NOT NULL COMMENT 业务主键由全局序列生成, order_time DATETIME NOT NULL COMMENT 下单时间同时是分片字段, user_id BIGINT NOT NULL COMMENT 用户ID, amount DECIMAL(12,2) NOT NULL COMMENT 订单金额, PRIMARY KEY (id), KEY idx_order_time (order_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有一个常见误用有人在第一个物理库建了临时表就让应用开始写入结果同一张逻辑表在另外两个节点上根本不存在路由过去后直接报 table doesnt exist。多分片环境里DDL 变更脚本要写成循环执行的形式每张物理表同步执行。这是我最早踩过的坑也是分片环境运维和单库运维最不一样的地方。4.3 节点数怎么定数据分布和循环周期的权衡PartitionByMonth 的取模空间就是数据节点数所以节点数直接决定两件事一是每个分片最多积累多少数据量二是数据循环回退的周期多长。如果节点数设为 12每个分片承载一个月的数据13 个月后新数据写回第 1 个节点第 1 个节点里就有两个完整月份的数据。如果业务数据量大这样的循环会让某些分片持续膨胀最终比单表还难维护。常见的做法是节点数设置为 12 的整数倍比如 24 或 36让每个分片只承载半个月或三分之一月的数据或者保持一月一节点但配合归档任务把超过 12 个月的物理分片从逻辑配置中摘掉。另一个决策点是物理分片用节点下标还是业务月份命名。我的偏好是用 dn1、dn2 这类哑编号命名 dataNode 和物理库把dn1 代表哪个月的数据完全交给规则计算而不是把库名写成 logdb_202401。这样调整节点数或者迁移分片时不用改物理库名只改 schema.xml 的映射即可灵活性高很多。5. 按月分片排坑与常见问题最容易翻车的 5 个场景这一章把我见到过的、以及自己踩过的坑整理成现象、原因、解决三步按严重程度排序。每一条都对应一个真实发生过的问题照着检查能省下不少半夜回滚的时间。5.1 日期格式不匹配点查变成全分片广播现象一条按天点查的 SQL 本应只访问一个分片慢查询日志里却出现三个物理库同时执行同一条 SQL。原因应用层传的日期字符串是 2025/03/12rule.xml 里 dateFormat 配置的是 yyyy-MM-ddPartitionByMonth 解析失败后被迫退回全分片广播。解决统一应用传参与 dateFormat 的格式包括时间段查询的边界是闭区间还是开区间在第一次对接时就约定好。改完 rule.xml 需要重启 Mycat 生效这类约定最好写进开发规范里让所有团队共用一套日期格式。5.2 sBeginDate 起始月设成部署当月历史数据路由错乱现象上线半年后补录的历史订单查询时用 explain 发现路由到了错误的节点直接查不到数据。原因sBeginDate 设成了部署当月导致所有更早日期的月份差为负数。负数取模在 Java 里可能是负下标Mycat 找不到对应数据节点报错或者路由错乱都出现过。解决sBeginDate 必须取业务最早可能出现数据的那个自然月宁可早一个月也不要晚。数据迁移上线时先查一下源表的最小日期再决定这个值填什么。5.3 分片字段为 NULL写入直接报 cant find datanode现象某个写入入口没有传 order_timeinsert 语句执行时报 cant find any valid datanode。原因路由阶段拿不到可解析的日期值PartitionByMonth 无法计算目标分片下标这条 SQL 就失去了路由依据。解决物理表 DDL 将分片字段设为 NOT NULL应用层在写入前做默认值兜底。同时排查这个入口为什么没传值通常背后是一个被忽略的接口字段缺失兜底只是防御手段。5.4 多表 JOIN 分片键不一致跨节点聚合失败现象order_log 与 order_detail 关联查询时Mycat 报跨节点关联不支持或者查询变得极慢。原因两张表的分片字段或者分片规则不一致同一笔订单的主表和明细可能落到不同物理节点Mycat 无法在本地完成 JOIN。解决要么把明细表的分片字段也设为 order_time保证同月份数据在同一个节点要么使用 Mycat 的 ER 关系配置把 order_detail 配置成 order_log 的 childTable。前者改动最小适合订单这种按时间访问的场景后者适合明细表必须独立分片的场景。5.5 漏配全局序列三个分片的主键全部从 1 开始现象插入数据没有报错但后续查询发现逻辑主键重复多个物理表中都出现 id1 的记录。原因每个物理 MySQL 的 auto_increment 各自独立从 1 开始递增分片后主键必然冲突。解决在 server.xml 里配置全局序列Mycat 支持本地时间戳、数据库、自定义等多种方案。对订单表这种需要回显主键的场景我一般用数据库方式保证严格递增对日志类不关心主键顺序的场景本地时间戳方式就够用。要注意逻辑表的主键生成必须交给 Mycat 处理应用不能再自行传 id否则序列配置形同虚设。6. 进阶验证技巧给按月分片数据做个体检配置全部落地后别急着庆祝。我给自己定了一条规矩每套分片方案上线前都要跑完三遍体检确认之后再让业务流量进来。体检第一项是路由精确性检查。把三种代表性 SQL 用 EXPLAIN 各跑一遍按月点查、按月范围查询、不带分片字段的查询。前两种必须路由到单节点第三种如果广播要明确这是有意的全量查询还是漏了条件。体检第二项是分片数据量分布检查。登录三个物理库分别执行下面这条 SQL对比各分片的行数和容量-- 分别在三个物理库执行对比分片数据分布 SELECT logdb_01 AS db_name, COUNT(*) AS row_count, ROUND(SUM(data_length index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema logdb_01 AND table_name order_log UNION ALL SELECT logdb_02, COUNT(*), ROUND(SUM(data_length index_length) / 1024 / 1024, 2) FROM information_schema.tables WHERE table_schema logdb_02 AND table_name order_log UNION ALL SELECT logdb_03, COUNT(*), ROUND(SUM(data_length index_length) / 1024 / 1024, 2) FROM information_schema.tables WHERE table_schema logdb_03 AND table_name order_log;按月分片天然允许各节点数据量不均衡这里要看的是有没有某个分片远超容量阈值。如果 3 月是旺季dn3 数据量是 dn1 的两倍属于预期但如果某个分片已经逼近物理磁盘容量就该考虑增加节点或者把历史分片归档出去了。体检第三项是一致性抽查。对近三个月的订单从逻辑库按 id 查一条再直连对应物理库按相同条件查一条比对结果一致。这一步不复杂但能发现隐藏的配置错误。最后讲一个自己的教训作为收尾最早做分片时我随手把 sBeginDate 填成部署当天三个月后补录历史数据全部路由错乱那次是连夜回滚才扛过去。之后我把 rule.xml 里每个属性的取值逻辑都写进了上线前检查单再也没有因为分片配置翻过车。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?