












在现代应用架构中,数据库性能和可扩展性是决定系统稳定性和高效性的关键因素。对于使用 MySQL 的系统来说,ProxySQL 是一个优秀的开源中间件,能够实现连接池管理、查询路由、读写分离以及隐藏真实数据库节点等强大功能。
本文记录了我从“如何在广域网环境中安全暴露单一数据库”开始,到一步步将其扩容为“主从架构读写分离”的实际解决过程。希望这篇实战记录能帮助你快速上手并搭建一套高可用的 ProxySQL 代理层。
在我的实际业务场景中,有几台部署在不同云厂商(广域网环境)的应用服务器,需要访问一台集中管理的 MySQL 数据库。
如果直接将数据库的 3306 端口暴露给公网,面临以下严重问题:
为了解决这些问题,我引入了 ProxySQL 作为协议层代理。ProxySQL 是一个专门为高并发和大规模数据库集群设计的轻量级中间层,具备以下核心优势:
为了方便运维管理和迁移,我采用 docker-compose 来部署 ProxySQL。
在宿主机上创建配置文件和数据挂载目录
mkdir -p ./proxysql/data
touch ./proxysql/proxysql.cnf
提示:如果 ./proxysql/proxysql.cnf 为空文件,ProxySQL 启动时会自动加载内置默认配置。你也可以直接使用官方提供的标准模版。
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 的配置系统分为三层,实现了“运行时修改”与“持久化落盘”分离:
[ RUNTIME ] <-- 当前内存生效的配置(不可直接修改)
▲
│ LOAD / SAVE
▼
[ MEMORY ] <-- 管理接口 (6032) 修改的配置区域
▲
│ LOAD / SAVE
▼
[ 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 数据库暴露给广域网服务。
登录后端真正的 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;
在宿主机上使用本地 mysql 客户端连接 ProxySQL 的管理接口:
mysql -h 127.0.0.1 -P 6032 -u admin -padmin
在 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;
我们将后端 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;
在 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;
在广域网应用服务器上,连接 ProxySQL 的 6033 代理端口:
mysql -h <ProxySQL_IP> -P 6033 -u app_user -p'AppPass123!' app_db
连接成功!此时应用访问的是 ProxySQL 的 6033 端口,真实的数据库 3306 端口成功被隐藏到了内部网络中。
当后续业务访问量增大时,我在后端新增了一台 MySQL 从库(192.168.1.101:3306),主从配置好数据同步后,希望 ProxySQL 自动把读请求分发给从库,写请求发给主库。
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 账号(主从复制通常会自动同步过来)。
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),从而自动路由至主库。
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 时,有几个踩坑点需要特别注意:
SAVE … TO DISK;,否则容器重启后内存中(MEMORY/RUNTIME)的更改会全部丢失。admin/admin 密码。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。