
手头这个项目编号463核心任务一句话就能说完给一套 MySQL 加 Mycat 中间件把读写分离跑起来。听起来很简单但实际做完一圈从主从复制、Mycat 配置文件、到路由验证、再到线上踩坑里面的细节足够写一篇像样的实践总结了。这篇不算教程更像一个完整的操作记录把我在 463 项目里的每一步选择、每一项配置、每一个坑都摊开讲适合正在规划读写分离的开发者、卡在 Mycat 配置上的人还有想评估“要不要引入中间件”的架构师参考。先说结论Mycat 做读写分离最大的价值是对应用层完全透明。业务代码不用动只需要把数据库连接地址从直连 MySQL 改成 Mycat 的 8066 端口剩下的读写路由全部由中间件负责。听起来很美好但前提是你得把主从架构、配置项、路由策略都搞清楚否则你会遇到“读写分离不生效”“事务里读到旧数据”“连接认证失败”这些奇怪问题。1. 读写分离要解决什么从一个压测事故说起1.1 读多写少的业务到底多到什么程度需要中间件463 项目本身是一套典型的读多写少系统用户量不大但查询接口特别多列表页、详情页、统计报表绝大多数请求都是 SELECT。一开始单机 MySQL 扛得很稳直到某次促销活动压测主库 CPU 直接打满慢查询成片出现。当时看了下监控读请求占比超过 90%写请求很少但所有流量都压在同一个实例上主库既要做写入又要响应大量查询IO 和 CPU 双双成为瓶颈。这种情况最直接的办法就是读写分离把查询流量从主库剥离开让 SELECT 走从库INSERT / UPDATE / DELETE 继续走主库。主库压力一旦降下来整个系统的吞吐量立刻上一个台阶。但这里有个很现实的问题业务代码里几十个 DAO、上百个查询 SQL如果靠手写逻辑去区分“这条查询走从库、那条写操作走主库”工作量巨大而且极容易出错。所以才会轮到 Mycat 这类数据库中间件登场。它部署在应用和 MySQL 之间伪装成一个 MySQL 服务器应用层只需要改一个连接地址。Mycat 拿到 SQL 之后根据语句类型自动做路由SELECT 分发到读节点DML 分发到写节点。这才是“中间件方案对比应用层方案”的核心优势侵入性小改造快符合老项目的实际处境。1.2 为什么选 Mycat 而不是其他方案当时我们内部其实对比过几个方向方案一是应用层自己做读写分离比如用动态数据源切换方案二是在 MySQL 层面做集群方案方案三是上中间件。方案一最大的问题是业务侵入太深每个 DAO 都要指定数据源而且遇到事务、缓存一致性、嵌套调用的时候特别麻烦改造完还得重新测试全链路风险高。方案二则把复杂度转移到了数据库运维侧而且对只读节点的负载均衡管理不够灵活。方案三里其实也不只 Mycat 一个选择当时考虑过 ProxySQL、ShardingSphere 以及 Mycat。ProxySQL 性能很强但它的架构和 Mycat 有本质区别是一个纯粹的 MySQL 协议代理更偏运维侧ShardingSphere 是另一种思路以 SDK 形式嵌入应用功能更丰富但引入依赖之后应用要重新打包部署Mycat 则是独立部署的 Java 中间件基于 MySQL 协议实现配置灵活社区案例多而且我们团队之前有同事用过踩过坑的文档都还在。最后选 Mycat核心考量是“对现有业务影响最小”。Mycat 独立部署应用只改 JDBC URL 和驱动不引入任何代码层面的依赖回退也简单把连接地址改回去就完成了。对一个已经上线的项目来说这种“低风险改造路径”比任何技术新潮点都重要。1.3 读写分离的基本链路从一条 SQL 说起为了先把概念理清楚我画了一张脑内链路图应用发起一条 SQL → 连接 Mycat 的 8066 端口 → Mycat 解析 SQL 类型 → 如果是 SELECT就把请求路由到配置好的读节点从库如果是 INSERT / UPDATE / DELETE就路由到写节点主库→ MySQL 主库通过主从复制把变更同步到从库。这个链路里有两个关键点容易被忽略。第一Mycat 是“伪装”成 MySQL 的所以应用侧驱动不需要额外安装MySQL 的 JDBC 驱动就能连第二主从复制是 Mycat 读写分离的数据基础如果主从延迟严重或者复制中断读节点上的数据就是旧的所以主从复制本身就是整个方案的地基必须花时间做扎实。下面就从地基开始讲。2. 环境规划与主从搭建把地基打牢2.1 节点规划与版本选型463 项目实际用的版本组合是 MySQL 5.7.36 和 Mycat 1.6.7.4。为什么会用这个组合原因很简单线上 MySQL 已经跑着 5.7不愿意为了读写分离去升级到 8.0Mycat 1.6.7.4 是现阶段最稳定、文档最多的 1.6 版本。如果你是新项目用 MySQL 8.0 也没问题但要注意认证插件的适配这个后面我会单独讲。节点规划上我建议至少准备三台机器角色地址示例部署组件说明MySQL 主库192.168.10.10MySQL 5.7负责写入开启 binlogMySQL 从库192.168.10.11MySQL 5.7负责查询开启 binlogMycat 中间件192.168.10.20Mycat 1.6.7.4部署读写分离路由层如果你只有两台机器可以把 Mycat 和从库部署在一起但我建议生产环境尽量分开毕竟 Mycat 也是要消耗 CPU 和内存的流量大的时候别让中间件和数据库互相抢资源。Mycat 本身是 Java 进程分配 2~4G 堆内存比较常见具体看你的并发量。2.2 主从复制的关键配置不要漏掉 server-id搭建 MySQL 主从复制步骤本身不复杂但有几个坑几乎是必踩的。第一步是修改主库配置文件关键内容如下[mysqld] server-id10 log-binmysql-bin binlog-formatrow gtid_modeon enforce_gtid_consistencyon注意server-id必须和从库不一样并且建议有规律比如主库用 10从库用 11这样以后节点多了也好排查。binlog-formatrow是行级复制比 statement 更安全遇到不带 WHERE 条件的 UPDATE、DELETE 也不会产生数据不一致。GTID 模式是 MySQL 5.6 以后引入的能让主从切换的定位工作简单很多强烈建议开启。从库的配置类似但server-id要换一个[mysqld] server-id11 log-binmysql-bin binlog-formatrow gtid_modeon enforce_gtid_consistencyon配置修改完成后重启 MysSQL 服务。然后在主库创建一个复制专用账号不要直接用 root权限最小化CREATE USER repl192.168.10.% IDENTIFIED BY repl_pass; GRANT REPLICATION SLAVE ON *.* TO repl192.168.10.%; FLUSH PRIVILEGES;接着确认主库的 binlog 坐标。如果用了 GTID就可以跳过 File 和 Position 那一套从库直接指定 MASTER_AUTO_POSITION1 就行。这里有个细节如果你的主库已经跑了很久数据量很大做复制之前通常先把数据全量导到从库。463 项目因为是相对新的库数据量不大我就直接让从库自动从空库开始同步但对已有大量数据的库建议用mysqldump --master-data2或者 XtraBackup 做一次基线备份。从库初始化CHANGE MASTER TO MASTER_HOST192.168.10.10, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDrepl_pass, MASTER_AUTO_POSITION1; START SLAVE;启动后一定要马上验证复制状态SHOW SLAVE STATUS\G重点关注两个字段Slave_IO_Running: Yes和Slave_SQL_Running: Yes。只要有一个是 No复制就没起来。常见原因无非是 server-id 冲突、账号权限不对、主库 binlog 没开、防火墙挡了 3306 端口。查一下LAST_SQL_ERROR字段一般就能定位。2.3 主从同步验证写一条数据读两边复制搭建完成后我习惯先做个最简单的验证在主库建一个测试库、一张表插入一条记录然后在从库查询能查到就说明 IO 线程和 SQL 线程都正常-- 主库执行 CREATE DATABASE appdb; USE appdb; CREATE TABLE user_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO user_info(name) VALUES (master_test);-- 从库查询 USE appdb; SELECT * FROM user_info;看到数据从主库同步到从库主从链路就算通了。这个阶段别看 Mycat先确认 MySQL 这层是靠谱的否则后面所有问题都会混在一起排查起来非常痛苦。3. Mycat 核心配置详解从 server.xml 到 schema.xml3.1 先认识 Mycat 的三个配置文件Mycat 1.6 的配置目录在conf/下面三个核心文件分别是server.xml、schema.xml、rule.xml。很多人第一次接触会被这三个文件搞晕其实职责很清晰server.xml定义 Mycat 对外连接的用户、密码、逻辑库名以及系统参数相当于“谁能连进来、能访问哪个库”。schema.xml定义逻辑库、逻辑表与真实 MySQL 节点的映射关系以及读写分离、负载均衡策略是读写分离的核心配置。rule.xml定义分片规则。如果只是做读写分离而不做水平分片rule.xml 基本不用改甚至可以不关心。server.xml里我做的配置大概是这样的user nameappuser defaultAccounttrue property namepasswordapppass/property property nameschemasappdb/property property namedefaultSchemaappdb/property /user这里有个小坑schemas必须和schema.xml里定义的逻辑库名一致否则连接时会报 “Invalid DataSource”。另外defaultAccounttrue表示用户使用默认账号客户端连接时如果没指定 database也会落到defaultSchema。3.2 schema.xml读写分离映射关系的核心schema.xml是整篇文章的重点。先放一份完整的配置再一行一行拆解?xml version1.0? !DOCTYPE mycat:schema SYSTEM schema.dtd mycat:schema xmlns:mycathttp://org.opencloudb/ schema nameappdb checkSQLschemafalse sqlMaxLimit100 table nameuser_info primaryKeyid dataNodedn1/ /schema dataNode namedn1 dataHosthost1 databaseappdb / dataHost namehost1 maxCon1000 minCon10 balance1 writeType0 dbTypemysql dbDrivernative switchType1 heartbeatselect user()/heartbeat writeHost hostmaster url192.168.10.10:3306 usermycatconn passwordmycat123 readHost hostslave1 url192.168.10.11:3306 usermycatconn passwordmycat123 weight1 / /writeHost /dataHost /mycat:schemadataNode定义的是数据节点理解成“逻辑库对应的真实 MySQL 实例上的一个物理库”。这里dataNode namedn1表示一个逻辑节点连接到host1这台机器上的appdb库。dataHost是真正定义读写分离策略的地方。先看balance属性这是读写分离的总开关balance 值行为0不启用读写分离所有读写都走 writeHost1读请求在 writeHost 与 readHost 之间轮询分发2所有读请求只在 readHost 之间分发writeHost 只负责写3读请求在所有 writeHost 与 readHost 中随机分发463 项目里我配置的balance1。为什么不用 2因为从库只有一台如果从库挂了balance2 模式下读请求会全部失败而 balance1 模式下读请求还能自动落到主库上相当于多了一层降级保障。如果你的从库数量不止一台且你能接受“从库挂了读就失败”的场景用 balance2 也可以。我更推荐生产环境用 balance1稳妥优先。再看writeType。Mycat 中writeType0表示所有写操作发送到第一个 writeHost如果第一个挂了由 Mycat 自动切换到第二个 writeHost。另一个值是 1表示写操作随机分发到所有 writeHost这种方式很少用除非你是多主架构。我们这里单主单从所以 writeType0 就对了。switchType控制主备切换。改成 1 表示根据心跳自动切换配合heartbeat标签使用。heartbeat里的select user()是 Mycat 用来检测后端 MySQL 是否存活的探活 SQL执行频率默认有配置也可以通过readHeartbeat/writeHeartbeat子属性单独设置。再说一个很容易忽略的地方writeHost里面的readHost部署在 writeHost 节点内部表示“从库从属于主库”。Mycat 在做负载均衡的时候readHost 只跟它所在的 writeHost 绑定。如果你配置了多个 writeHost每个 writeHost 下面都可以挂自己的 readHost这样的模型对“多主多从”的场景非常有用。checkSQLschema这里说一句。它表示是否检查 SQL 里的 schema 名。如果我们连接 Mycat 的库名是appdb而 SQL 里又写了SELECT * FROM appdb.user_infoMycat 能不能解析checkSQLschemafalse表示不检查让这类带库名前缀的 SQL 原样透传有时候会在后端报语法错误。如果是 trueMycat 在分片时会把表名前的 schema 去掉再路由。老项目里历史 SQL 如果习惯带库名前缀建议设成 true 省得踩坑。3.3 数据源账号Mycat 连接 MySQL 用的不是应用账号配置文件里writeHost和readHost的user/password是 Mycat 用来连接后端 MySQL 的账号不是应用连接 Mycat 的账号两者要分开。我在 MySQL 里创建了一个专用账号CREATE USER mycatconn192.168.10.% IDENTIFIED BY mycat123; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO mycatconn192.168.10.%; GRANT SELECT ON mysql.* TO mycatconn192.168.10.%; FLUSH PRIVILEGES;注意最后一条Mycat 心跳语句是select user()这个不需要额外权限但如果你用了别的探活方式比如select * from information_schema.tables就需要相应权限。从最小权限原则出发appdb.*的增删改查足够了别给 root。MySQL 8.0 有一个大坑默认认证插件是caching_sha2_password而 Mycat 1.6 自带的驱动对它的支持有兼容问题连接时会报错比如Access denied for user或者Unable to connect。解决方法是给 Mycat 的专用账号指定老的认证插件CREATE USER mycatconn192.168.10.% IDENTIFIED WITH mysql_native_password BY mycat123; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO mycatconn192.168.10.%; FLUSH PRIVILEGES;这个细节在 463 项目里真真实实地浪费了我一晚上。如果你用的是 MySQL 5.7完全没有这个问题但首次上手 Mycat 的人大概率会用 8.0 测试所以提前注意。3.4 启动 Mycat常见启动方式与日志观察Mycat 安装好之后启动方式很简单cd /usr/local/mycat/bin ./mycat start ./mycat status如果启动失败第一时间去看两个日志文件tail -100 /usr/local/mycat/logs/wrapper.log tail -100 /usr/local/mycat/logs/mycat.logwrapper.log记录的是 JVM 进程的启停信息Mycat 起不来多半能在这里看到原因比如端口被占用、JVM 参数不对、配置文件 XML 格式错误。mycat.log记录的是路由和 SQL 执行的业务日志。我遇到过最典型的启动失败原因是修改server.xml或者schema.xml时标签没闭合XML 解析直接失败这时候wrapper.log里会抛 SAXParseException找起来很快。启动完成后确认 Mycat 监听了 8066 端口netstat -tlnp | grep 8066然后用 MySQL 客户端做连接测试。注意端口是 8066不是 MySQL 的 3306mysql -h192.168.10.20 -P8066 -uappuser -papppass -Dappdb能进去说明 Mycat 对外这个口子已经通了。4. 实操把读写分离真正跑起来4.1 准备工作准备好测试表和测试数据正式验证 read / write 分离前先把测试环境准备好。主库上建一张表并插入几条数据CREATE TABLE user_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, source VARCHAR(32) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO user_info(name, source) VALUES (data_1, master), (data_2, master), (data_3, master);这里的source字段是我专门设计用来标识数据来源的。主从同步正常的情况下从库也会出现同样的三条数据。为了区分查询到底落在哪个节点上我会在从库上手工插入一条特殊数据-- 从库执行模拟“只有在从库才存在”的数据 INSERT INTO user_info(name, source) VALUES (data_only_on_slave, slave);注意当主从复制正常工作的状态下从库是不应该允许写入的除非你设置了 log_slave_updates 并让从库可写。我这里只是为了验证路由效果临时插入的一条记录正常生产环境千万不要这么干。4.2 从应用视角验证SELECT 走读库写操作走写库应用连接 Mycat 端口后执行查询SELECT * FROM user_info WHERE id 1; SELECT * FROM user_info WHERE name data_only_on_slave;第一条能查到数据第二条也能查到说明查询请求确实落到了从库——因为data_only_on_slave在从库才有。如果没有走从库这条记录根本查不到。这是最直观的验证方式。然后执行写操作INSERT INTO user_info(name, source) VALUES (data_from_mycat, mycat);再跑到主库上查SELECT * FROM user_info WHERE name data_from_mycat;数据在主库出现了说明 INSERT 被路由到写节点。同样的道理UPDATE、DELETE 也应该验证一遍。4.3 看 Mycat 日志确认路由决策过程除了从业务结果验证更硬核的方式是看 Mycat 日志。在 Mycat 机器上执行tail -f /usr/local/mycat/logs/mycat.log然后在客户端执行一条 SELECT观察日志的输出。Mycat 1.6 的日志默认输出 DEBUG 级别的路由信息能看到类似下面的内容executeSQL: select * from user_info where id 1 route: dn1, sql: select * from user_info where id 1再执行一条 INSERTexecuteSQL: insert into user_info(name, source) values (data_from_mycat, mycat) route: dn1, sql: insert into user_info(name, source) values (data_from_mycat, mycat)只看这些还不够直观。想确认 SELECT 具体到的是主库还是从库我建议在后端 MySQL 上打开通用日志general_log分别观察主从库的请求接收情况SET GLOBAL general_log ON; SET GLOBAL log_output TABLE;然后从 Mycat 执行一条查询再分别在主库、从库执行SELECT * FROM mysql.general_log ORDER BY event_time DESC LIMIT 10;如果从库的 general_log 里看到了这条 SELECT主库里没有那就非常确定读请求被路由到了从库。验证完记得关闭 general_log这个日志在生产环境非常吃性能SET GLOBAL general_log OFF;4.4 强制走主库的三种方法读写分离有一个隐患你刚写入的数据主从还没来得及同步紧接着的查询如果走从库就会读到旧的或者缺失的数据。这种问题在“先写后读”的业务场景里几乎必现比如用户注册后立刻查询自己的资料。Mycat 提供了几种强制查询走主库的办法。第一种是加 Mycat 注解直接在 SQL 前加一段注释/*!mycat:db_typemaster*/ SELECT * FROM user_info WHERE id 1;Mycat 看到这个注解就会把这条 SQL 路由到写节点。db_typeslave则强制走从库。第二种方式是在事务内执行读操作。Mycat 对事务的处理逻辑是一旦开启了事务事务内的所有 SQL 都绑定到写节点直到提交或者回滚。所以如果你业务里用的是 Spring 的Transactional在事务里查数据天然就会走主库。这也是为什么很多项目里在事务里做读写分离会遇到“为什么都不走从库”的疑惑——这是 Mycat 故意这样设计的。第三种方式是配置全局表或某些特定表不走读写分离。在 schema.xml 里可以指定部分表的dataNode如果单独把这些表指向主库所在的 dataNode那么它们的所有 SQL 都只会走写节点。这个做法适合那些一致性要求极高、或者数据量不大但被频繁读写的配置表。实际项目里我的习惯是默认查询走从库所有实时性要求高的查询加上db_typemaster注解事务内查询不做任何特殊处理。简单、直接、可控。4.5 验证过程中的真实记录中间件的学习门槛比想象中低整个 463 项目的读写分离验证从开始搭建主从到 Mycat 配置完成、再到路由验证通过一共花了不到半天。最花时间的环节反而不是 Mycat而是 MySQL 主从复制上的 GTID 配置和账号认证。Mycat 本身的学习曲线主要集中在 schema.xml 的数据结构理解上一旦想通了dataHost → writeHost → readHost这个层级关系配置起来很快。当然这里说的“很快”是在你有清晰配置文档的前提下。如果没有配置经验直接上生产我强烈建议先在测试环境完整走一遍尤其是把故障场景模拟出来比如把从库停掉观察 Mycat 的行为是否符合预期再决定要不要上生产。5. 常见问题与排查技巧实录读写分离方案上线这么长时间我整理的这几个问题最容易被问到也是新手最容易踩的坑。5.1 读写分离不生效SELECT 还是打到主库遇到这个问题先检查schema.xml里的balance属性是不是设成了 0。balance0 是关闭读写分离的这是最蠢也最常见的错误。其次确认你是不是真的通过 Mycat 的 8066 端口连的数据库而不是直接连了 MySQL 的 3306。很多“读写分离不生效”的案例最终定位发现客户端配置里端口根本没改。最后再确认后端从库是否配置正确如果 readHost 的地址写错Mycat 会认为从库不可用直接把读流量转到主库表现就是“看起来读写分离没生效”。如果需要精确判断 Mycat 后端连接池里每个连接的归属可以登录 8066 端口执行 Mycat 的管理命令SHOW DATANODE; SHOW BACKEND;SHOW BACKEND能看到当前 Mycat 和后端 MySQL 建立的连接以及每个连接所在的 dataHost 和地址。5.2 事务里的读请求被路由到从库这其实是一个对 Mycat 行为理解的误区。默认情况下Mycat 对事务内的读请求会路由到写节点所以事务内读到的数据是主库的最新数据。但如果你的应用没有走事务而是用自动提交模式一条一条发 SQL那 SELECT 就会被路由到从库。比如你执行完INSERT之后马上执行SELECT如果没有强制主库注解这个 SELECT 很大概率会落在从库而此时从库可能还没同步完。这种情况的解决方案就两个一是通过注解强制走主库二是在代码层面对“写后读”场景做缓存等主从同步完成再去读。我的建议是第一优先用注解简单直接第二个方案涉及业务改造复杂度较高。5.3 主从延迟导致的“读不到刚写的数据”主从延迟是读写分离方案的天敌。延迟来自两个层面网络层面和数据量层面。网络层面如果从库和主库不在同一机房延迟会明显偏高数据量层面如果主库有大批量更新从库的 SQL 线程可能跟不上。缓解措施有几种把主从放在同一个内网降低网络延迟使用半同步复制插件确保事务在主库提交后至少有一个从库收到 binlog在从库上调整参数比如并行复制副本数提高 SQL 线程的吞吐能力。从业务侧解决最直接的办法是把实时性要求高的查询强制走主库牺牲一部分读性能来换一致性。这也是大多数项目的最终选择。5.4 Mycat 连接 MySQL 8.0 认证失败这个我前面说过MySQL 8.0 默认的caching_sha2_password认证插件和 Mycat 1.6 自带驱动不兼容。解决方式是在 MySQL 里把 Mycat 账号的认证插件改成mysql_native_password。如果你的数据库已经建好了账号可以用ALTER USER mycatconn192.168.10.% IDENTIFIED WITH mysql_native_password BY mycat123; FLUSH PRIVILEGES;另外一个相关的问题是时区设置。Mycat 和 MySQL 之间的时区不一致可能导致CURRENT_TIMESTAMP写入的时间对不上或者返回的时间差了 8 小时。建议在 Mycat 的连接 URL 或者 MySQL 的 JDBC 参数里显式指定时区让两边统一。5.5 连接数被打满Mycat 拒绝服务Mycat 自身也有连接池maxCon控制的是一台后端 MySQL 上最多建立的连接数。如果业务并发较高maxCon 配置小了会发现 Mycat 频繁报连接不够用的错误。这时候需要调大 maxCon。但要注意Mycat 面对的 MySQL 也有 max_connections 限制如果 Mycat 单节点连接数提升了MySQL 那边也要同步调整否则就是 Truncated 的错误。问题常见原因排查方向读写分离不生效balance 配了 0或客户端直连 MySQL检查 schema.xml、检查连接端口事务内读到旧数据没走事务且未强制主库使用注解强制主库主从延迟大网络延迟、大批量更新内网部署、半同步复制、并行复制Mycat 连接 8.0 失败认证插件不兼容改用 mysql_native_password连接数打满maxCon / max_connections 不匹配同步调整连接池参数另外还遇到过一个小问题Mycat 的sqlMaxLimit如果设置了值会在执行的 SELECT 语句后面自动拼 LIMIT。但如果你业务 SQL 本身已经有 LIMIT或者用到了FOR UPDATE这个自动拼限可能产生奇怪的行为。读写分离场景里如果是纯查询影响不大但如果带着FOR UPDATE的查询被拼上 LIMITMySQL 会直接报错。遇到这类问题要注意排查别在 schema.xml 里随手加这个限制。结尾再分享一点实操体会做 463 项目的时候我一开始想得太复杂认为读写分离是件很庞大的事结果真正落地的时候发现核心链路只有这么短主从复制 Mycat 配置 验证。但越是链路短地基越重要。主从复制没做好后面一切白搭Mycat 配置里的 balance 策略不琢磨清楚上线之后总会出幺蛾子。我个人最大的体会是不要指望 Mycat 帮你解决一切它的定位就是一个老实本分的“SQL 路由器”你给它的配置越明确它表现就越稳定。如果你正准备在自己的项目里做读写分离我给三个建议第一从库数量起步不要太多一台就够了先把链路跑通再考虑扩容第二强制走主库的注解要提前规范好让团队形成统一约定避免“写后读”的脏数据问题到处冒头第三监控和告警一定要提前配主从延迟、Mycat 连接池状态、读库存活情况这些指标比业务代码更能反映系统的真实健康度。把这三件事做在前面读写分离这个方案才能真正帮你解压而不是反过来成为新的不稳定因素。