MyCat 分库分表从入门到实战:核心配置与分片规则详解
做后端开发的应该都有过这种体验业务量上来了单库单表的 MySQL 扛不住读写延迟飙升。你说加缓存吧热点数据还能顶一顶加索引吧慢查询变少了但数据量到千万级、上亿级之后再好的索引也救不了物理存储的瓶颈。这时候大部分团队会走上分库分表这条路而 MyCat 就是这条路上一把非常顺手的工具。我最早接触 MyCat 是在一个做电商订单系统的项目里订单量每天百万级单库撑不住又不想把业务代码全部改成访问分片的客户端组件于是选择了 MyCat 这种中间层代理方案。用了之后最大的感受是它对研发团队来说学习曲线真的很友好只要把数据源地址从 MySQL 改成 MyCat 的 8066 端口然后写好分片规则剩下的 SQL 大部分照常写。这篇就以“学习第一天”的视角把 MyCat 的核心概念、配置文件、分片操作和分片规则一次讲透适合刚接触分库分表、想快速上手分布式数据库中间件的同学。1. 先搞明白 MyCat 的基本盘1.1 从单库瓶颈到中间件代理单机数据库为什么扛不住其实不光是磁盘空间的问题。数据量一大B 树索引的层数越来越高随机 IO 的代价越来越大写入时的锁竞争、binlog 同步、buffer pool 的命中率都会直线下滑。很多团队最初用主从复制、读写分离来缓解但这只解决了读压力写入瓶颈依旧卡在单机上。分库分表正是把一张大表按某种规则拆到多个 MySQL 实例上让每台机器只承担一部分数据。但问题来了业务端怎么知道数据在哪个库分页查询怎么跨节点聚合多表关联怎么办如果把这些全部写进业务代码每一处查询都要手写路由逻辑那才是灾难。MyCat 的定位就是把这些事情集中到中间层处理。它伪装成一个 MySQL 数据库业务层照常发 SQLMyCat 帮你解析 SQL、找数据节点、把结果合并返回。对应用层来说它就是一个“超级大 MySQL”这样业务代码的改动量就小很多。1.2 MyCat 在链路中的位置与核心能力从部署架构看MyCat 是挡在应用和 MySQL 集群之间的一层代理本身不存数据真正存数据的还是后端的 MySQL 实例。它内置了一个 SQL 解析器能识别 select、insert、update、delete也能解析多表 join 和聚合函数然后根据配置好的分片规则把 SQL 路由到对应的 dataNode数据节点。核心能力拆开看主要有四个读写分离、分库分表、全局序列、结果聚合。读写分离解决的是多个 MySQL 实例之间读写的调度分库分表是最核心的数据水平扩展手段全局序列解决分片后自增主键冲突的问题结果聚合则处理跨分片的排序、分页、求和等操作。我第一次用的时候最惊讶的是它对普通查询的透明性。比如一条 select count(*) 如果涉及多个分片MyCat 会把 count 下推到每个分片执行再汇总结果返回业务层完全无感知。当然这不是说所有 SQL 都能透明化后面讲分片规则时我会专门说哪些操作有坑。1.3 学习路径与版本选型MyCat 有两个主要版本线1.6 系列和 2.0 系列。2.0 在设计上做了很多重构但社区生态、资料数量、生产案例目前还是 1.6 更成熟绝大多数公司生产环境用的也是 1.6 的某个分支版本。所以学习阶段建议直接用 1.6 最新的 release 版本坑少文档多出了问题也好搜。另外要区分两个概念MyCat 和 MyCat-Proxy、MyCat2 的关系。网上很多文章把几个项目混着说容易把人绕晕。简单理解1.6 就是我们平时说的经典版核心代码是 Java 写的2.0 是新一代架构兼容性上还在追赶。如果你是第一天学直接聚焦 1.6 的配置思路和分片规则即可这些核心概念换到任何分库分表中间件上都通用。我后面的示例全部基于 1.6 配置格式。2. 三个核心配置文件逐段拆解MyCat 的全部逻辑都体现在 conf 目录下的一组 XML 文件里其中最重要的三个是 server.xml、schema.xml、rule.xml。不少人刚开始看到这三个文件头大其实只要抓住一条主线就好server.xml 管“谁能连”schema.xml 管“数据长什么样”rule.xml 管“数据怎么分”。2.1 server.xml用户权限与系统参数server.xml 里定义的是 MyCat 对外暴露的虚拟账号。客户端连 MyCat 时用的用户名、密码以及这个账号能访问哪些逻辑库都在这里配置。user nameroot defaultAccounttrue property namepassword123456/property property nameschemasTESTDB/property property namereadOnlyfalse/property /user这个配置的意思是用户 root密码 123456可以访问名为 TESTDB 的逻辑库。readOnly 设成 false 表示允许写操作。实际生产环境不建议用这么简单的账号建议为应用单独创建账号并分配最小权限。server.xml 里还有一块是 system 参数区里面有一些影响 MyCat 行为和性能的全局参数比如 maxCon、processorCount、sequnceHandlerType 等。第一天学习不需要全记但有一个要留意sequnceHandlerType这是全局序列的实现方式我放到后面分片规则部分展开讲。新手最容易忽略的是防火墙配置conf 目录下的 firewall.xml 可以配置 IP 白名单生产环境一定要开否则任何一个知道 8066 端口的人都能连上来。2.2 schema.xml逻辑库与数据节点的映射关系schema.xml 是整个 MyCat 配置里信息量最大的文件它负责把“逻辑库/逻辑表”映射到“物理数据库/物理表”。先看一个最基础的结构schema nameTESTDB checkSQLschematrue sqlMaxLimit100 table nameorders dataNodedn1,dn2,dn3 rulemod-long / /schema dataNode namedn1 dataHosthost1 databasedb_order_1 / dataNode namedn2 dataHosthost1 databasedb_order_2 / dataNode namedn3 dataHosthost1 databasedb_order_3 / dataHost namehost1 maxCon1000 minCon10 balance0 writeType0 dbTypemysql dbDriverjdbc switchType1 heartbeatselect user()/heartbeat writeHost hostnode1 url127.0.0.1:3306 usermycat_user passwordmycat_pwd readHost hostnode2 url127.0.0.1:3307 usermycat_user passwordmycat_pwd weight1 / /writeHost /dataHost拆开来看schema 标签定义一个逻辑库 TESTDB里面有一张逻辑表 orders。这张表不在某个固定库里而是分布在 dn1、dn2、dn3 三个数据节点上分片规则用的是 mod-long后面会讲。dataNode 则是具体的物理落点比如 dn1 对应 host1 这台 MySQL 上的 db_order_1 库。dataHost 是真正连接后端 MySQL 的入口。writeHost 是写库readHost 是读库两者可以配置主从关系balance 参数控制读写分离的负载均衡策略。heartbeat 是 MyCat 用来检测后端 MySQL 存活状态的探活 SQL一般写 select user() 就行。新手容易踩坑的地方dataHost 里的 url 指向的是物理 MySQL 的连接地址不要写成 MyCat 自己的地址否则会循环代理。我第一次配置就犯了这错误结果 MyCat 启动报错提示后端连接不上。schema.xml 里还有两个几乎每个生产配置都会碰到的属性checkSQLschema 和 sqlMaxLimit。checkSQLschema 设为 true 时MyCat 会帮你自动去掉 SQL 里的库名前缀比如 use TESTDB 之后再发 select * from TESTDB.orders它会处理成 select * from orders 再路由。sqlMaxLimit 则给查询加一个默认返回行数限制避免一条不带 limit 的 SQL 把多个分片的数据全拉回来导致内存爆掉。2.3 rule.xml分片规则的静态配置rule.xml 和 schema.xml 的关系很紧密。schema.xml 里引用了一个规则名比如 mod-long那 rule.xml 里就必须定义这个名字对应的 tableRule同时 tableRule 里要引用具体的 function。看配置tableRule namemod-long rule columnsid/columns algorithmmod-long/algorithm /rule /tableRule function namemod-long classio.mycat.route.function.PartitionByMod property namecount3/property /functiontableRule 里的 columns 表示用哪一列作为分片键algorithm 指向 function 的名字。function 里 class 是具体的分片算法类property 是算法参数比如 PartitionByMod 的 count 就表示取模的基数 3。也就是说schema.xml 里 orders 表的 mod-long 规则最终效果是按照 id 对 3 取模得到 0、1、2分别路由到 dn1、dn2、dn3。理解这个三层引用关系非常关键schema.xml 的表被拆到哪些节点、用什么规则最终由 rule.xml 决定。很多刚接触的人配置分片表只改了 dataNode没有在 table 标签里加 rule结果 MyCat 把这张表当成了不分片的单表只有第一个 dataNode 能查到数据。这个问题我建议第一天就好好验证一遍方法下文会讲。3. 分片操作实战全流程光看配置不实操过两天就忘。这一节我用一个标准的分片操作流程带着你走一遍从建库到验证数据分布保证每一步都看得懂、跑得通。3.1 环境规划与初始化先规划一个最小可用的测试环境。准备两台 MySQL 实例或者在一台机器上开两个端口模拟比如 3306 和 3307。每个实例上建立三个物理库db_order_1、db_order_2、db_order_3再在每个库里建一张相同结构的表 ordersCREATE TABLE orders ( id BIGINT NOT NULL, user_id BIGINT NOT NULL, order_amount DECIMAL(10,2), create_time DATETIME, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;三个库表结构相同但数据会按照分片规则分散到不同库里。然后给 MyCat 创建一个后端连接账号授权范围覆盖这三个库比如 mycat_user。接着下载 MyCat 1.6 的安装包解压后修改 conf 目录下的三个 XML 文件。这一步的目的就是让 MyCat 启动时能正确连上 3306、3307 两个 MySQL 后端并识别那三个物理库。配置完成后启动 MyCatcd mycat/bin ./mycat start ./mycat statusstatus 显示 running 就算启动成功。如果想看更详细的日志去 logs 目录看 wrapper.log 和 mycat.log。启动失败时90% 的线索都在 mycat.log 里不要只看终端输出。3.2 配置逻辑库与分片表环境起来后连接 MyCat。注意是 8066 端口不是 3306mysql -uroot -p123456 -h127.0.0.1 -P8066这条命令进去之后你会发现可以直接用 SQL 操作 TESTDB。先建一张分片表试试USE TESTDB; CREATE TABLE orders ( id BIGINT NOT NULL, user_id BIGINT NOT NULL, order_amount DECIMAL(10,2), create_time DATETIME, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有一个非常关键的细节MyCat 建表时只会在后端三个物理库里各执行一次建表语句也就是三张物理表都会建好。这和普通 MySQL 建表完全不一样因为它在逻辑层面“广播”给了所有 dataNode。建完之后可以用 MySQL 客户端分别连一下 3306、3307 的两个实例确认三个库里都有 orders 表。如果你在 schema.xml 里没有给 orders 配 rule那 MyCat 会按默认规则把它当成全量复制表或单节点表处理表只在第一个 dataNode 里创建。判断方法很简单看建完表之后三个物理库里是否都有这张表。如果只有 db_order_1 有那说明 rule 没生效往回检查 schema.xml。3.3 插入数据验证分片效果表建好之后插入几条测试数据INSERT INTO orders (id, user_id, order_amount, create_time) VALUES (1, 101, 10.00, NOW()), (2, 102, 20.00, NOW()), (3, 103, 30.00, NOW()), (4, 104, 40.00, NOW()), (5, 105, 50.00, NOW()), (6, 106, 60.00, NOW());按 mod-long 规则id 对 3 取模id1、4 落到 db_order_1id2、5 落到 db_order_2id3、6 落到 db_order_3。然后去后端 MySQL 分别检查-- 在 db_order_1 执行 SELECT * FROM orders; -- 在 db_order_2 执行 SELECT * FROM orders;实际观察到的结果应该和预期一致每张物理表里都只存了 2 条数据。这就是分片的本质逻辑上是一张大表物理上拆成了多份。通过 MyCat 查询时它会把三张表的数据合并成一份返回SELECT * FROM orders ORDER BY id;这个操作看上去和单库没区别但背后 MyCat 做的事情是把 SQL 发往三个 dataNode每个节点各自执行 select 并返回数据MyCat 汇总后排序再返回给客户端。如果数据量很大跨分片 order by 的性能开销不小这也是为什么分片表设计时尽量要保证同分区内的数据操作能本地完成。3.4 结果合并与跨分片查询做完基础分片我建议再试几个聚合操作观察 MyCat 的处理能力。比如SELECT COUNT(*) FROM orders; SELECT SUM(order_amount) FROM orders;MyCat 会把这个 SQL 下推到每个分片执行 count 或 sum再把结果汇总相加。这算是中间件代理的一个优势你不必在业务层写循环遍历分片再聚合的代码。不过有得有失如果查询条件里没有分片键MyCat 就必须全表路由即把 SQL 发到所有 dataNode 上执行比如 select * from orders where user_id 101 这条语句由于分片键是 idMyCat 无法根据 user_id 定位到具体分片只能广播到三个节点最后合并。数据量一大这种全表扫描式的查询会成为性能隐患。所以分片键的选择极其重要。原则很简单查询频率最高的条件是什么就用什么列做分片键。如果业务里经常按 user_id 查订单就应该把 orders 的分片键改成 user_id而不是按自增 id 取模。这是分库分表设计里最值得花时间想清楚的一件事。第一天学习时你可以故意写一条不带分片键的查询观察日志里的路由节点数量对比一下带分片键的查询感受差异。4. 分片规则选型与原理分片规则是 MyCat 最灵活的部分官方提供了十几种 function每个都有适用场景。第一天不要求全记住但要理解每种规则的核心逻辑和取舍这样以后业务要分片时你能快速判断用什么规则。4.1 常用分片规则对比先看一张规则能力对照表规则名称核心算法适用场景潜在问题mod-long对分片键取模数据离散度要求高id 均匀分布扩容时需要重新分布数据hash-int按枚举值映射分片地区、渠道等有限枚举枚举值分布不均会倾斜auto-sharding-long按数值范围落入分片时间、ID 范围查询多热点集中在新范围murmur一致性哈希大规模集群扩容频繁范围查询支持较弱按日期分片按月/日切割日志、流水数据历史分区无写入浪费空间选择规则时最核心的一点是预判数据的访问特征和增长方式。比如订单系统如果用户量增长快、需要频繁扩容建议使用一致性哈希或按 user_id 取模如果是日志系统写入集中在当前时间按天/月分片最自然如果是配置类数据数据量不大且几乎不增长甚至不需要分片做成全局表就行。4.2 取模分片的原理与配置取模分片是学习 MyCat 必碰的入门规则上面已经演示过配置。它的数学逻辑非常简单分片结果 分片键 % 分片数量。优点是算法简单、数据分布均匀缺点是分片数量一旦变化几乎所有数据的映射关系都会变化这意味着扩容时基本要把全量数据重新分布一遍。实际配置里count 参数必须和 schema.xml 里的 dataNode 数量一致。比如 dataNode 配了 4 个count 却写成 3那余数为 3 的那部分数据就没有节点可归会直接路由失败。我的建议是先数清楚 schema.xml 里有几个节点再填写 count。并且取模基数不要随意变更生产环境要做扩容时要提前规划新的 count 值设计好数据迁移方案。另一个配置小技巧是取模分片最好配合全局序列使用。因为取模分片不保证 id 全局唯一如果直接用 MySQL 的 auto_increment每个分片都从 1 开始自增必然出现多个分片 id 相同。MyCat 的全局序列功能可以解决这个问题后面单独讲。4.3 范围分片与枚举分片范围分片用 id 或时间落在哪个区间来决定数据去哪个节点。比如 auto-sharding-long 规则function namerang-long classio.mycat.route.function.AutoPartitionByLong property namemapFileautopartition-long.txt/property /functionmapFile 里配置范围和节点的映射关系比如 0-500M0、500M-1000M1。这种规则的好处是范围查询非常高效因为同一范围内的数据大概率在同一个节点上MyCat 不需要跨分片聚合并排序。坏处是如果数据一直单调增长比如 id 或时间戳一直在变大新数据就会集中写入最后一个分片热点问题严重。枚举分片则是把一个离散值映射到固定节点典型场景是地区。比如华东数据放节点 0华北放节点 1function namehash-int classio.mycat.route.function.PartitionByFileMap property namemapFilepartition-hash-int.txt/property property nametype1/property /functiontype1 表示 key 是整数type0 表示 key 是字符串。mapFile 里每一行是“枚举值节点索引”。这个规则有个致命注意点未在枚举映射里命中的数据MyCat 会抛异常还是路由到默认节点取决于配置里有没有写 defaultNode。如果业务里新增了一个枚举值忘了更新 mapFile那部分数据就等于被“卡住”写不进去了。所以生产里用枚举分片一定提前规划好枚举值的变化机制。4.4 一致性哈希与全局序列一致性哈希是很多大规模集群的默认选择MyCat 里对应 murmur 规则。它的核心思想是把哈希值空间看成一个环节点也分布在环上数据按哈希值找到最近的节点。和取模相比增加或减少节点时只需要迁移少量数据扩容成本低很多。代价是哈希分布天然不支持范围查询范围扫描会落到多个节点排序和分页会变慢。全局序列则是分片表绕不开的话题。MyCat 提供了多种实现方式1.6 里最常用的是数据库方式和本地时间戳方式。数据库方式的原理是维护一张专门的序列表每次取号时更新并返回当前值配置项如下property namesequnceHandlerType1/propertysequnceHandlerType 为 1 表示数据库方式需要在某个库中建立 MYCAT_SEQUENCE 表和对应存储函数为 2 表示本地时间戳方式这种方式利用时间戳加机器标识生成全局唯一 ID不依赖额外表但生成的 ID 是雪花算法风格对业务可见不适合敏感的连续 ID 场景。实际项目里如果你的业务主键不要求有序推荐直接用时间戳方式省事如果有严格的连续序号要求需要额外设计序列服务MyCat 内置的数据库方式性能和并发能力都比较弱不太适合核心链路。5. 常见问题与排查实录这一节是实际操作中踩坑的经验总结。我见过不少新手把一天时间耗在配置上就是因为不懂看日志、不懂定位路由问题。下面列几个我处理过的高频问题你可以直接对照排查。5.1 连不上 MyCat 或权限报错用 mysql 客户端连 8066 报 Access denied第一反应不是改 MySQL 后端账号而是去看 server.xml 里的 user 配置。MyCat 连接使用的前端账号是它自己管理的和后端 MySQL 账号无关。它的后端账号才是 schema.xml 的 dataHost 里配置的那个。分成两套账号体系理解问题就清晰了。如果 MyCat 能启动但连接后执行 SQL 报错“Invalid DataSource”那问题大概率出在 dataHost 配置上。检查 dataHost 里的 url、user、password 是否能正常连上后端 MySQL以及 heartbeat 探活是否成功。看 mycat.log 里有没有类似 connection refused 的关键字。还有一点容易被忽略后端 MySQL 账号的 host 授权如果后端账号只允许 localhost 登录而 MyCat 部署在另一台机器连接自然会被拒绝。5.2 分片数据分布不均有时候按取模规则建完表插入后却发现某些节点数据特别多某些节点没有数据。这时候要查两件事第一分片键的取值是否均匀。比如分片键是订单状态字段无非是“待付款、已付款、已发货、已完成”取模后必然集中在几个节点。第二是否多个表共用了同一个 rule 配置导致所有表都按同样的方式分布。如果业务确实需要按状态分片建议改成枚举分片或按时间分片或者干脆换用 user_id 这类高离散度的列做分片键。另外要注意一个常见误解分片表和普通表不能混查。如果你的逻辑库既配了分片表又配了全局表那全局表必须在每个 dataNode 上都建一份物理表并且 schema.xml 里通过 typeglobal 标识。不然 join 查询时会因为某个分片缺少这张表而报错。5.3 跨分片 JOIN 与分页性能问题跨分片 join 是中间件方案的软肋。MyCat 能支持部分 join但会消耗额外的内存做数据关联数据量一大就容易慢。设计阶段最常见的规避手段是“ER 分片表”即在 schema.xml 中把关联表配置在同一个分片键上让关联数据落在同一个节点table nameorders dataNodedn1,dn2,dn3 rulemod-long childTable nameorder_items joinKeyorder_id parentKeyid / /table意思是 order_items 表的 order_id 与 orders 表的 id 关联并且 order_items 使用相同的分片规则这样同一个订单的主表和明细表始终在同一个分片内join 操作就能本地完成。我在项目里遇到过最典型的性能问题就是没有配置 ER 关系订单主表和明细表各自分片结果查订单详情时 MyCat 强制跨节点关联几万条数据就把内存打满了。所以凡是有关联关系的表一定要从设计阶段就规划好分片键的关联性。分页问题同理。select * from orders order by id limit 10MyCat 的处理方式是先把每个分片各自排序取前 10 条然后把三组数据汇总再排序取前 10 条结果是对的。但如果你要 offset 很大的深分页比如 limit 100000, 20每个分片都要取出前 100020 条再合并内存和耗时都会剧增。这是所有分库分表方案的通病解决思路一般是用“上一页最后一条记录”代替 offset 翻页或者让分页查询走到只能落到单分片的查询条件上。最后分享一个很多人不知道的小经验MyCat 的 explain 功能很好用。在 SQL 前面加上 explain 关键字MyCat 会打印出这条 SQL 实际路由到了哪些 dataNode、用了什么规则。排查分片问题时这是最直接的诊断工具比反复查日志高效得多。例如EXPLAIN SELECT * FROM orders WHERE id 1;你会看到 “DataNode : dn1” 之类的输出说明这条查询只路由了 dn1。如果能看到多个 dn 节点说明这是一条全表路由的 SQL性能风险一眼就看出来了。第一天学完建议你把自己写的 SQL 都过一遍 explain哪些是单分片执行、哪些是全节点广播做到心里有数。我个人的体会是分库分表这件事复杂不等于高级能用单分片解决的查询绝不要让它跨节点。学习 MyCat 时最难的不是配置文件怎么填而是建立起“路由意识”——写每条 SQL 之前下意识判断它会落在哪个节点、会扫描多少数据。有了这种意识再回头理解取模、范围、哈希这些规则就会觉得顺理成章。这套知识不光适用于 MyCat你以后换任何分库分表中间件都会有底气。