ProxySQL选型实战:从手写读写分离到中间件的踩坑全记录 关键词ProxySQL、数据库中间件、读写分离、MyCAT、ShardingSphere、主从复制、查询路由、高可用 大家好我是数据库小学妹我们前面学完主从复制、读写分离感觉动态数据源、AOP 注解、强制读主这条路终于走通了。后面发现在Spring 配了两套数据源事务里还要手动处理加个从库就得改代码重新发版。连接池管理、故障切换这些更麻烦的事我压根没考虑到。最近我就把市面上主流的数据库中间件翻了个遍最后选了 ProxySQL。今天把选型和踩坑的过程捋一遍省得你再走我的弯路。一、手写读写分离的痛点初学读写分离时手写代码确实轻量好用。但系统跑起来之后问题一个个冒出来痛点手写代码中间件新增从库改代码、发版、重启改配置秒级生效从库故障代码判断连接失败再切主库自动摘除故障节点连接池每个数据源单独配统一管控多语言Java 写一套Python 再写一套任何语言连过来都行负载均衡自己实现轮询/权重内置支持说白了中间件就是在应用和数据库之间加一层代理脏活累活它全包了。二、选型为什么是 ProxySQL市面上做读写分离的中间件我重点看了三个ProxySQL、MyCAT、ShardingSphere。维度ProxySQLMyCATShardingSphere定位轻量级 MySQL 代理分布式数据库中间件生态最全的数据库中间件部署复杂度低单进程中依赖 ZooKeeper高概念多、配置复杂读写分离原生支持规则灵活支持支持分库分表不支持支持强项故障自动切换内置健康检查需额外部署需配合其他组件配置方式SQL 语句配置XML 配置YAML/Java API性能损耗很低C 开发中等中等学习曲线平缓中等陡峭适合场景纯读写分离分库分表分库分表 企业级需求我当时只需要读写分离没有分库分表的需求。MyCAT 和 ShardingSphere 功能太重为了一个读写分离引入一套复杂架构成本和收益不成正比。ProxySQL 轻量、专注、性能好够用就行。当然如果你已经在用 ShardingSphere 做分库分表直接用它做读写分离也顺理成章。选型没有绝对对错看现状。三、ProxySQL 核心概念动手之前先搞清楚几个核心概念不然配置的时候会一脸懵。3.1 三层配置体系ProxySQL 的配置分三层这是最容易搞混的地方┌─────────────┐ │ RUNTIME │ ← 正在生效的配置内存中最快 ├─────────────┤ │ MEMORY │ ← 你正在编辑的配置还没生效 ├─────────────┤ │ DISK │ ← 持久化到 SQLite 的配置重启不丢 └─────────────┘操作逻辑改 MEMORY → LOAD 到 RUNTIME → SAVE 到 DISK。刚开始我老是忘了 SAVE重启 ProxySQL 后配置全没了又得重新配一遍 3.2 几个关键表ProxySQL 的配置存在表里不是配置文件这点和传统中间件很不一样表名作用mysql_servers后端 MySQL 实例主库、从库都在这登记mysql_users应用连接 ProxySQL 用的账号mysql_query_rules核心定义读写分离规则mysql_replication_hostgroups主从组别管理自动故障切换用四、实战从零搭起来4.1 Docker 启动dockerrun-d\--nameproxysql\-p6033:6033\-p6032:6032\proxysql/proxysql:2.5# 6033 是应用连接端口6032 是管理端口连接管理端口开始配置mysql-uadmin-padmin-h127.0.0.1-P6032--promptProxySQL 4.2 添加后端 MySQL 实例-- 添加主库写节点INSERTINTOmysql_servers(hostgroup_id,hostname,port,weight,comment)VALUES(10,mysql-master,3306,1000,主库-写);-- 添加从库读节点INSERTINTOmysql_servers(hostgroup_id,hostname,port,weight,comment)VALUES(20,mysql-slave1,3306,500,从库1-读);INSERTINTOmysql_servers(hostgroup_id,hostname,port,weight,comment)VALUES(20,mysql-slave2,3306,500,从库2-读);-- 加载到 RUNTIME 并持久化LOADMYSQL SERVERSTORUNTIME;SAVEMYSQL SERVERSTODISK;hostgroup_id是分组用的10 是写组20 是读组。权重weight决定流量分配比例两个从库都是 500流量就是对半分。4.3 配置应用账号-- 添加应用连接账号应用用这个连 ProxySQLINSERTINTOmysql_users(username,password,default_hostgroup)VALUES(app_user,app_pass,10);LOADMYSQL USERSTORUNTIME;SAVEMYSQL USERSTODISK;default_hostgroup10的意思是默认请求都走写组主库除非后面的规则明确指定读组。4.4 核心配置读写分离规则这是最关键的一步规则决定了哪些 SQL 走主库、哪些走从库。-- 规则1SELECT 且不在事务中 → 走读组20INSERTINTOmysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply)VALUES(1,1,^SELECT.*,20,1);-- 规则2事务中的 SELECT → 要走主库保证一致性INSERTINTOmysql_query_rules(rule_id,active,match_digest,match_pattern,destination_hostgroup,apply)VALUES(2,1,^SELECT.*FOR UPDATE,10,1);-- 规则3写操作INSERT/UPDATE/DELETE→ 走写组10INSERTINTOmysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply)VALUES(3,1,^(INSERT|UPDATE|DELETE),10,1);-- 规则4默认兜底走写组INSERTINTOmysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply)VALUES(4,1,.*,10,1);LOADMYSQL QUERY RULESTORUNTIME;SAVEMYSQL QUERY RULESTODISK;规则按rule_id从小到大匹配匹配到就停止。所以顺序很重要先匹配 SELECT FOR UPDATE要读主再匹配普通 SELECT读从再匹配写操作最后兜底走主库。五、验证效果应用连接改成 ProxySQL 的地址spring:datasource:url:jdbc:mysql://proxysql:6033/mydbusername:app_userpassword:app_pass然后用stats_mysql_query_digest表查看路由情况SELECTdigest_text,sum_time,count_star,hostgroupFROMstats_mysql_query_digestORDERBYcount_starDESCLIMIT10;如果看到 SELECT 的hostgroup是 20INSERT/UPDATE 的hostgroup是 10恭喜读写分离生效了 ✅六、踩坑实录血泪史 坑 1事务里的读操作被路由到从库现象一个事务里先 INSERT 了一条数据紧接着 SELECT 查出来是空的。原因SELECT 被规则匹配到读组去从库查了但从库还没同步完这条数据主从延迟。解决开启事务时ProxySQL 会自动把所有请求路由到同一个 hostgroup默认是事务开始的那个。但我当时没用事务包裹就出问题了。正确做法涉及写后立即读的逻辑要么放事务里要么在 SQL 前加注释强制走主库/* hostgroup10 */SELECT*FROMordersWHEREuser_id123; 坑 2从库挂了流量没自动切走现象一个从库宕机后ProxySQL 还在往上面发请求导致部分查询报错。原因没配健康检查或者检查间隔太长。解决用mysql_replication_hostgroups表让 ProxySQL 自动管理主从状态INSERTINTOmysql_replication_hostgroups(writer_hostgroup,reader_hostgroup,comment)VALUES(10,20,主从自动管理);LOADMYSQL SERVERSTORUNTIME;SAVEMYSQL SERVERSTODISK;配上之后从库挂了会自动摘流量主库挂了会触发切换配合 MGR 或 Orchestrator 效果更好。 坑 3规则写得太宽泛漏匹配现象有些查询没被规则匹配到全走到了默认的主库从库闲置。原因match_digest用的是正则^SELECT.*看起来能匹配所有 SELECT但如果 SQL 里有换行或者注释就可能匹配不上。解决用SELECT ... FOR UPDATE这种明确的模式做精确匹配普通 SELECT 放最后兜底。规则宁可写细一点别贪多。 坑 4忘了 SAVE 到 DISK现象重启 ProxySQL 后所有配置都没了。解决每次改完配置记得SAVE MYSQL ... TO DISK;。后来我写了个脚本改完自动 LOAD SAVE再也没丢过配置。 坑 5监控没跟上出问题了才知道现象ProxySQL 本身挂了应用全连不上半小时后才被发现。解决监控 ProxySQL 的关键指标ProxySQL_Threadpool_TrxNum当前事务数mysql_server_ping_errors后端节点健康状态stats_mysql_connection_pool连接池使用情况配合 Prometheus GrafanaProxySQL 出问题能秒级告警。七、总结选型这件事说难也难说简单也简单。我目前只需要读写分离没有分库分表的需求。ProxySQL 做不了分库分表但恰好够用这就是最合适的选择。手写代码做读写分离短期轻松长期痛苦。中间件看似多引入一层省掉的是后面无限叠加的维护成本。规则顺序和事务一致性是读写分离最容易翻车的两个地方。配规则的时候多测几遍别等上线了再翻车。 我是数据库小学妹一个用设计师思维学数据库的转行人。你们在读写分离选型上踩过什么坑本文基于 ProxySQL 2.5 MySQL 8.0 环境。不同版本配置略有差异建议参考官方文档确认参数。