在现代应用架构中,数据库性能和可扩展性是决定系统稳定性和高效性的关键因素。对于使用 MySQL 的系统来说,ProxySQL 是一个优秀的开源中间件,能够实现连接池管理、查询路由、读写分离以及隐藏真实数据库节点等强大功能。
本文记录了我从“如何在广域网环境中安全暴露单一数据库”开始,到一步步将其扩容为“主从架构读写分离”的实际解决过程。希望这篇实战记录能帮助你快速上手并搭建一套高可用的 ProxySQL 代理层。
为什么需要 ProxySQL?(实战背景)
在我的实际业务场景中,有几台部署在不同云厂商(广域网环境)的应用服务器,需要访问一台集中管理的 MySQL 数据库。
如果直接将数据库的 3306 端口暴露给公网,面临以下严重问题:
- 安全隐患:真实数据库 IP 地址暴露在公网,容易遭受暴力破解或攻击。
- 权限粒度粗:很难在数据库入口前再加一层精细化的流量控制与账户隔离(例如只共享某一个库,限制最大连接数)。
- 扩展困难:未来如果业务量增长需要做数据库读写分离,应用层代码必须修改连接池或数据库配置,侵入性极强。
为了解决这些问题,我引入了 ProxySQL 作为协议层代理。ProxySQL 是一个专门为高并发和大规模数据库集群设计的轻量级中间层,具备以下核心优势:
- 高吞吐与连接池:内置高性能连接池,大幅减少频繁连接/断开数据库产生的开销。
- 零代码侵入读写分离:根据 SQL 语法自动分发读写请求,应用只需连接 ProxySQL 的代理端口。
- 动态配置机制:绝大多数配置可以在运行时通过 SQL 命令动态修改,无需重启服务。
环境搭建:使用 Docker Compose 部署
为了方便运维管理和迁移,我采用 docker-compose 来部署 ProxySQL。
准备目录与初始配置文件
在宿主机上创建配置文件和数据挂载目录
mkdir -p ./proxysql/data
touch ./proxysql/proxysql.cnf
提示:如果 ./proxysql/proxysql.cnf 为空文件,ProxySQL 启动时会自动加载内置默认配置。你也可以直接使用官方提供的标准模版。
编写 docker-compose.yml
version: '3.8'
services:
proxysql:
image: proxysql/proxysql:2.7.0
container_name: proxysql
restart: on-failure:5
# 暴露端口:6033 为业务连接端口,6032 为 ProxySQL 管理端口
ports:
- "6033:6033" # MySQL 代理访问端口
- "127.0.0.1:6032:6032" # 管理端口(强烈建议仅监听本地 127.0.0.1)
volumes:
- ./proxysql/proxysql.cnf:/etc/proxysql.cnf
- ./proxysql/data:/var/lib/proxysql
networks:
- database_net
networks:
database_net:
driver: bridge
启动容器:
docker-compose up -d
确认容器运行状态
docker ps | grep proxysql
ProxySQL 核心概念解析
在正式配置之前,有两套关键概念必须理解:“三套用户”与“多层配置架构”。
三套用户系统
在 ProxySQL 中,三套用户分别指管理用户、应用用户和监控用户,它们具有不同的权限和作用,适用于不同的场景。
- 管理用户(Admin User):
- 作用:连接 6032 管理端口,修改 ProxySQL 本身的内部配置。
- 默认凭据:用户名 admin,密码 admin。
- 监控用户(Monitor User):
- 作用:ProxySQL 用来向后端真实的 MySQL 节点发送心跳和探活查询的用户。需要在后端 MySQL 上提前创建好。
- 应用用户(Application User):
- 作用:业务应用程序连接 ProxySQL(6033 端口)使用的凭据。ProxySQL 也会拿着对应的凭据去代理访问后端的 MySQL。
多层配置架构与操作指令
ProxySQL 的配置系统分为三层,实现了“运行时修改”与“持久化落盘”分离:
[ RUNTIME ] <-- 当前内存生效的配置(不可直接修改)
▲
│ LOAD / SAVE
▼
[ MEMORY ] <-- 管理接口 (6032) 修改的配置区域
▲
│ LOAD / SAVE
▼
[ DISK / CONFIG FILE ] <-- 磁盘 SQLite 数据库及配置文件
- RUNTIME:代表 ProxySQL 当前正在使用的物理内存配置。不能直接修改,必须从 MEMORY 层 LOAD 进来。
- MEMORY:通过 6032 端口连接 ProxySQL 管理 SQL 接口时修改的就是这一层。随意修改不会直接影响正在运行的业务。
- DISK & CONFIG FILE:磁盘持久化存储(内嵌的 SQLite 数据库)。用于保证重启后配置不丢失。
核心流转指令
在 6032 管理终端中,配置修改的标准流程是:修改 MEMORY -> LOAD 到 RUNTIME -> SAVE 到 DISK。

常用命令模式如下:
LOAD TO RUNTIME;—— 将配置从内存加载到运行时生效SAVE TO DISK;—— 将配置保存到磁盘数据库LOAD FROM DISK;—— 从磁盘加载配置到内存LOAD FROM CONFIG;—— 从配置文件加载配置到内存
(其中 可以是 MYSQL SERVERS, MYSQL USERS, MYSQL QUERY RULES, MYSQL VARIABLES 等)
实战一:配置单节点代理(对外暴露单一数据库)
首先解决我最初的诉求:隐藏后端真实 MySQL(假设 IP 为 192.168.1.100:3306),只将 app_db 数据库暴露给广域网服务。
步骤 1:在后端 MySQL 上创建对应用户
登录后端真正的 MySQL 节点,创建监控用户和业务用户:
-- 1. 创建 ProxySQL 监控用户(仅需最低基础权限)
CREATE USER 'monitor'@'%' IDENTIFIED BY 'MonitorPass123!';
GRANT USAGE, REPLICATION CLIENT ON *.* TO 'monitor'@'%';
-- 2. 创建业务应用用户(按需仅授权 app_db 数据库)
CREATE USER 'app_user'@'%' IDENTIFIED BY 'AppPass123!';
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'%';
FLUSH PRIVILEGES;
步骤 2:登录 ProxySQL 管理终端 (6032)
在宿主机上使用本地 mysql 客户端连接 ProxySQL 的管理接口:
mysql -h 127.0.0.1 -P 6032 -u admin -padmin
步骤 3:配置监控账户信息
在 Admin 交互界面中,告诉 ProxySQL 监控用户的信息:
SET mysql-monitor_username='monitor';
SET mysql-monitor_password='MonitorPass123!';
SET mysql-monitor_ping_interval=2000; -- 2秒探活一次
-- 生效并持久化
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;
步骤 4:添加后端数据库节点
我们将后端 MySQL 添加到组 ID 为 10 的服务器组中(通常约定 10 为主库/写库组):
-- hostgroup_id = 10 表示主库组/单库组
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections)
VALUES (10, '192.168.1.100', 3306, 500);
-- 加载到运行内存并保存到磁盘
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
步骤 5:配置业务应用用户
在 ProxySQL 中配置允许连接的用户,并指定默认路由到的服务器组:
INSERT INTO mysql_users (username, password, default_hostgroup, active)
VALUES ('app_user', 'AppPass123!', 10, 1);
-- 加载到运行内存并保存到磁盘
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;
步骤 6:测试代理连接
在广域网应用服务器上,连接 ProxySQL 的 6033 代理端口:
mysql -h <ProxySQL_IP> -P 6033 -u app_user -p'AppPass123!' app_db
连接成功!此时应用访问的是 ProxySQL 的 6033 端口,真实的数据库 3306 端口成功被隐藏到了内部网络中。
实战二:扩展为 MySQL 主从读写分离
当后续业务访问量增大时,我在后端新增了一台 MySQL 从库(192.168.1.101:3306),主从配置好数据同步后,希望 ProxySQL 自动把读请求分发给从库,写请求发给主库。
ProxySQL 的主机组规划:
- Hostgroup 10:写节点组(Master)
- Hostgroup 20:读节点组(Slave)
步骤 1:在 ProxySQL 中添加从节点
登录 6032 管理端口,将从库加入 Hostgroup 20:
-- 添加 Slave 到组 20
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections)
VALUES (20, '192.168.1.101', 3306, 500);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
注意:从库上也需要确保包含前面创建的 monitor 和 app_user 账号(主从复制通常会自动同步过来)。
步骤 2:配置读写分离路由规则
ProxySQL 使用 mysql_query_rules 表中的正则表达式匹配 SQL,决定将其发往哪个主机组。
-- 清空历史规则(如果是全新配置)
DELETE FROM mysql_query_rules;
-- 规则 1:带有 FOR UPDATE 的 SELECT 语句必须走主库(Hostgroup 10)
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT.*FOR UPDATE$', 10, 1);
-- 规则 2:普通的 SELECT 语句全部发往从库组(Hostgroup 20)
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (2, 1, '^SELECT', 20, 1);
-- 应用规则并持久化
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
规则生效逻辑:默认情况下,未匹配到 ^SELECT 规则的所有语句(如 INSERT, UPDATE, DELETE)会使用 mysql_users 表中设置的 default_hostgroup(即组 10),从而自动路由至主库。
步骤 3:配置主从动态维护(可选推荐)
ProxySQL 能够自动根据 MySQL 的 read_only 状态维护主机组。防止主从切换时发生误写:
INSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup, comment)
VALUES (10, 20, 'Master-Slave Auto Failover Group');
LOAD MYSQL REPLICATION HOSTGROUPS TO RUNTIME;
SAVE MYSQL REPLICATION HOSTGROUPS TO DISK;
运维与监控常用指令
ProxySQL 内部提供了非常丰富的统计视图(stats 库)和监控视图(monitor 库),无需借助第三方工具即可查询运行状态。
查看后端节点状态
-- 查看当前运行中的后端服务器列表及其状态(ONLINE / SHUNNED / OFFLINE)
SELECT hostgroup_id, hostname, port, status, Queries, Latency_us FROM runtime_mysql_servers;
查看健康检查心跳日志
-- 查看 ProxySQL 对后端的 Ping 监控情况
SELECT hostname, port, time_start_us, rc, error FROM monitor.mysql_server_ping_log ORDER BY time_start_us DESC LIMIT 5;
验证读写分离命中情况
-- 查看路由规则命中统计
SELECT rule_id, hits FROM stats.stats_mysql_query_rules;
-- 查看 SQL 级别的汇总统计
SELECT digest_text, count_star, sum_time FROM stats.stats_mysql_query_digest ORDER BY count_star DESC LIMIT 10;
总结与注意事项
通过从“单库代理”到“读写分离”的平滑演进,ProxySQL 成功帮我解决了广域网数据库暴露的安全问题,并为高并发架构打下了良好的基础。
在实际使用 ProxySQL 时,有几个踩坑点需要特别注意:
- 修改必须持久化:在 6032 端口修改完配置后,务必执行
SAVE … TO DISK;,否则容器重启后内存中(MEMORY/RUNTIME)的更改会全部丢失。 - 读写分离一致性:对于在主库刚写入数据后立马要查出来的业务逻辑,如果读请求走从库,可能会因主从延迟导致查不到数据。这类特定 SQL 可以通过 SQL 注释或在 ProxySQL 中显式增加高优先级规则路由回主库。
- 管理端口安全:6032 端口拥有 ProxySQL 的绝对控制权,切勿直接暴露出公网,或者在部署后第一时间修改默认的
admin/admin密码。

