目录
[一、ProxySQL 简介](#一、ProxySQL 简介)
[1. 核心功能](#1. 核心功能)
[2. 什么时候用 ProxySQL](#2. 什么时候用 ProxySQL)
[(1)主从架构 + 读写分离(最典型)](#(1)主从架构 + 读写分离(最典型))
[(2)MySQL 集群(MGR/PXC/InnoDB Cluster)](#(2)MySQL 集群(MGR/PXC/InnoDB Cluster))
[(3)高并发、大流量(互联网 / 金融 / 电商)](#(3)高并发、大流量(互联网 / 金融 / 电商))
[1. 版本说明](#1. 版本说明)
[2. 下载与安装](#2. 下载与安装)
[3. 服务与管理接口](#3. 服务与管理接口)
[(1)使用 systemctl](#(1)使用 systemctl)
[(2)使用 ProxySQL 管理接口](#(2)使用 ProxySQL 管理接口)
[4. 配置文件](#4. 配置文件)
[三、配置 ProxySQL 对接 MySQL](#三、配置 ProxySQL 对接 MySQL)
[1. 环境说明](#1. 环境说明)
[2. 配置步骤](#2. 配置步骤)
[(4)添加 MySQL 用户](#(4)添加 MySQL 用户)
[1. 读写分离](#1. 读写分离)
[2. 读负载均衡](#2. 读负载均衡)
[1. 开启半同步复制](#1. 开启半同步复制)
[2. 修改查询规则](#2. 修改查询规则)
[(1)基于 SQL 语句分析的智能路由](#(1)基于 SQL 语句分析的智能路由)
[(2)将需要强一致性的 select 路由到主库](#(2)将需要强一致性的 select 路由到主库)
ProxySQL 是一款高性能、开源的 MySQL 协议代理中间件,它位于应用与 MySQL 集群之间,提供透明的流量管理、读写分离、负载均衡、连接池、故障转移等核心能力,极大提升了 MySQL 集群的性能、可用性与可扩展性。
一、ProxySQL 简介
1. 核心功能
(1)读写分离(最常用)
自动路由:
- 写请求(INSERT/UPDATE/DELETE/REPLACE)→ 主库(Master)
- 读请求(SELECT)→ 从库(Slave)
优势:无需修改业务代码,透明分流,显著降低主库压力。
(2)负载均衡(读库横向扩展)
- 支持多种算法:轮询、权重、最少连接、响应时间优先。
- 自动分发读请求到多个从库,提升读性能与吞吐量。
(3)连接池管理(解决连接风暴)
- 复用数据库连接,减少频繁建连/断连开销
- 控制连接数、超时、队列,避免 MySQL 连接数耗尽。
- 显著提升高并发场景下的稳定性与吞吐量。
(4)自动故障转移(高可用)
- 实时健康检查:探测主 / 从库状态、延迟、连接数。
- 故障自动切换:主库宕机时自动切到备主(触发自定义脚本),从库故障自动摘除。
- 业务无感知:无需人工介入,保证 99.99% 可用性。
(5)查询路由(灵活规则)
- 支持正则/规则路由:按库、表、用户、SQL 内容分流
- 典型场景:报表/分析查询 → 专用从库;热点表/大查询 → 专用节点。
(6)查询缓存(性能加速)
- 缓存高频、静态、可重复查询结果(如商品详情、配置)。
- 减少 DB 访问、降低延迟、提升 QPS。
(7)监控、限流与审计
- 实时监控:连接数、QPS、延迟、错误率、慢查询。
- SQL 限流:限制高危/大查询(如全表扫描、批量更新)。
- 审计日志:记录所有 SQL,便于安全审计与问题排查。
(8)动态配置(不停机运维)
- 在线修改配置(路由、权重、规则),无需重启 ProxySQL。
- 支持在线扩容/缩容数据库节点。
2. 什么时候用 ProxySQL
(1)主从架构 + 读写分离(最典型)
- 读多写少(电商、内容、用户中心、报表)。
- 主库压力大、读请求占比 >70%。
- 不想改业务代码实现读写分离。
(2)MySQL 集群(MGR/PXC/InnoDB Cluster)
- 多主/多从、需统一接入层。
- 集群节点动态扩容/缩容。
- 自动故障转移、保证业务连续。
(3)高并发、大流量(互联网 / 金融 / 电商)
- 连接数暴涨、MySQL 连接数不足。
- 峰值流量(秒杀、大促、报表)。
- 低延迟、高 QPS 要求。
(4)多租户/混合业务
- 多库多表、需统一路由。
- 不同业务隔离(交易、报表、日志)。
(5)简化运维、统一管理
- 统一入口:应用只连一个 ProxySQL,不用关心后端集群。
- 运维集中:监控、限流、路由、故障切换统一管理。
(6)云数据库(RDS/Aurora)
- 只读实例多、需负载均衡 + 读写分离。
- 自动故障切换、避免云厂商故障影响业务。
一句话总结:ProxySQL 是 MySQL 集群的 "交通枢纽":读写分离、负载均衡、连接池、故障转移、监控限流,让 MySQL 更稳、更快、更易扩展。
二、安装配置
1. 版本说明
ProxySQL 最新稳定版是 3.0.7,仅支持 CentOS 9/10、RHEL 9/10、AlmaLinux 9/10。CentOS 7 官方支持的最后版本是 2.7.3。
- 官方仓库:https://repo.proxysql.com/ProxySQL/proxysql-2.7.x/centos/7
- 对应 RPM:proxysql-2.7.3-1-centos7.x86_64.rpm
版本系列说明:
- 3.0.x (Stable Tier):2026 年最新稳定版,不再支持 CentOS 7。
- 2.7.x (Legacy Stable):CentOS 7 最后维护系列,最新版 2.7.3。
- 4.0.x (AI/MCP Tier):实验性 AI 版本,同样不支持 CentOS 7。
安装建议:
- CentOS 7:必须安装 2.7.3(无官方更新)。
- CentOS 9/10:可安装 3.0.7(最新稳定)。
2. 下载与安装
本例的操作系统为 CentOS Linux release 7.9.2009 (Core),因此安装 ProxySQL 2.7.3。
# 下载安装包
https://repo.proxysql.com/ProxySQL/proxysql-2.7.x/centos/7/proxysql-2.7.3-1-centos7.x86_64.rpm
# 安装依赖
yum install -y perl-DBI perl-DBD-MySQL
# 安装 proxysql
rpm -ivh proxysql-2.7.3-1-centos7.x86_64.rpm
# 启动 proxysql
proxysql -c /etc/proxysql.cnf
输出如下:
[root@kxvv-yz-mysqlproxy~]#proxysql -c /etc/proxysql.cnf
2026-04-20 09:55:37 [INFO] Using config file /etc/proxysql.cnf
2026-04-20 09:55:37 [INFO] Using jemalloc with MALLOC_CONF: config.xmalloc:1, lg_tcache_max:16, opt.prof_accum:1, opt.prof_leak:1, opt.lg_prof_sample:20, opt.lg_prof_interval:30, rc:0
2026-04-20 09:55:37 [INFO] Current RLIMIT_NOFILE: 512000
2026-04-20 09:55:37 [INFO] Using OpenSSL version: OpenSSL 3.3.1 4 Jun 2024
2026-04-20 09:55:37 [INFO] No SSL keys/certificates found in datadir (/var/lib/proxysql). Generating new keys/certificates.
[root@kxvv-yz-mysqlproxy~]#
- Using config file /etc/proxysql.cnf:成功加载了指定的配置文件。
- Using jemalloc with ...:使用高性能内存分配器 jemalloc,ProxySQL 默认用它提升性能。
- Current RLIMIT_NOFILE: 512000:系统允许 ProxySQL 最大打开 512000 个文件句柄,用于支持高并发连接。
- Using OpenSSL version: OpenSSL 3.3.1:使用 OpenSSL 加密库。
- No SSL keys/certificates found... Generating new keys:第一次启动,没有找到 SSL 证书,自动生成了一套。
查看进程:
[root@kxvv-yz-mysqlproxy~]#ps -ef | grep proxysql | grep -v grep
root 29773 1 0 09:55 ? 00:00:00 proxysql -c /etc/proxysql.cnf
root 29774 29773 0 09:55 ? 00:00:02 proxysql -c /etc/proxysql.cnf
[root@kxvv-yz-mysqlproxy~]#
- PID 29773(父进程/监控进程)由系统 init 启动(PPID=1),只负责检查子进程是否存活,若子进程崩溃,自动重启子进程,不处理业务流量。
- PID 29774(子进程/工作进程)由父进程 fork() 创建(PPID=29773),核心工作进程,处理所有 MySQL 连接、路由、查询,内部包含多线程(admin、worker、monitor 等)。
查看进程树:
[root@kxvv-yz-mysqlproxy~]#pstree -p | grep proxysql
|-proxysql(29773)---proxysql(29774)-+-{proxysql}(29775)
| |-{proxysql}(29776)
| |-{proxysql}(29777)
| |-{proxysql}(29778)
| |-{proxysql}(29779)
| |-{proxysql}(29780)
| |-{proxysql}(29781)
| |-{proxysql}(29782)
| |-{proxysql}(29783)
| |-{proxysql}(29784)
| |-{proxysql}(29785)
| |-{proxysql}(29786)
| |-{proxysql}(29787)
| |-{proxysql}(29788)
| |-{proxysql}(29789)
| |-{proxysql}(29790)
| |-{proxysql}(29791)
| |-{proxysql}(29792)
| `-{proxysql}(29793)
[root@kxvv-yz-mysqlproxy~]#
查看线程(子进程内部多线程):
[root@kxvv-yz-mysqlproxy~]#ps -T -p 29774
PID SPID TTY TIME CMD
29774 29774 ? 00:00:00 proxysql
29774 29775 ? 00:00:00 MyHGCU
29774 29776 ? 00:00:00 GTID
29774 29777 ? 00:00:00 Admin
29774 29778 ? 00:00:00 MySQLWorker0
29774 29779 ? 00:00:00 MySQLWorker1
29774 29780 ? 00:00:00 MySQLWorker2
29774 29781 ? 00:00:00 MySQLWorker3
29774 29782 ? 00:00:00 QueryCachePurge
29774 29783 ? 00:00:00 proxysql
29774 29784 ? 00:00:00 MonitorDNSCache
29774 29785 ? 00:00:00 MyMonStateData
29774 29786 ? 00:00:00 MyMonStateData
29774 29787 ? 00:00:00 MonitorConnect
29774 29788 ? 00:00:00 MonitorPing
29774 29789 ? 00:00:00 MonitorReadOnly
29774 29790 ? 00:00:01 MonitorGR
29774 29791 ? 00:00:00 MonitorGalera
29774 29792 ? 00:00:01 MonitorAurora
29774 29793 ? 00:00:00 MonitReplicLag
ps -T -p 进程号 能看到 ProxySQL 内部所有工作线程,输出的每一行都是 ProxySQL 内部的一个独立线程,各司其职。这就是 ProxySQL 高性能 + 高可用 的内部架构。
- 主线程
29774 29774 ? 00:00:00 proxysql
主入口线程,负责统筹。
- 管理线程(Admin)
29774 29777 ? 00:00:00 Admin
负责 6032 管理端口,登录 proxysql_admin 执行配置,就是它在处理。
- 工作线程(处理 MySQL 流量)
29774 29778 ? 00:00:00 MySQLWorker0
29774 29779 ? 00:00:00 MySQLWorker1
29774 29780 ? 00:00:00 MySQLWorker2
29774 29781 ? 00:00:00 MySQLWorker3
这是核心干活线程,处理所有应用发来的 SQL 请求、读写分离、路由、负载均衡,默认 4 个(可配置)。
- 监控线程(检查后端 MySQL 状态)
这是 ProxySQL 最关键的功能:自动监控后端数据库是否正常。
29774 29787 ? 00:00:00 MonitorConnect # 能不能连上
29774 29788 ? 00:00:00 MonitorPing # 心跳是否正常
29774 29789 ? 00:00:00 MonitorReadOnly # 是否只读
29774 29790 ? 00:00:01 MonitorGR # MGR 集群监控
29774 29791 ? 00:00:00 MonitorGalera # Galera 集群
29774 29792 ? 00:00:01 MonitorAurora # Aurora 监控
29774 29793 ? 00:00:00 MonitReplicLag # 复制延迟
这些线程轮询检查 MySQL,一旦发现主库挂了,自动切流量到从库。
- 缓存线程
29774 29782 ? 00:00:00 QueryCachePurge
查询缓存清理,负责过期缓存回收。
- 高可用、配置、状态同步线程
29774 29775 ? 00:00:00 MyHGCU
29774 29776 ? 00:00:00 GTID
29774 29784 ? 00:00:00 MonitorDNSCache
29774 29785 ? 00:00:00 MyMonStateData
29774 29786 ? 00:00:00 MyMonStateData
负责配置同步、GTID、DNS 缓存、状态数据。
查看端口:
[root@kxvv-yz-mysqlproxy~]#netstat -lntp | grep proxysql
tcp 0 0 0.0.0.0:6032 0.0.0.0:* LISTEN 29774/proxysql
tcp 0 0 0.0.0.0:6033 0.0.0.0:* LISTEN 29774/proxysql
[root@kxvv-yz-mysqlproxy~]#
0.0.0.0:6033 业务端口(程序连接 MySQL 用)
0.0.0.0:6032 管理端口(配置 ProxySQL 用)
3. 服务与管理接口
ProxySQL 安装完成后,可通过 systemctl 命令或管理接口(Admin interface)对进程进行管控。
(1)使用 systemctl
# 启动
systemctl start proxysql
# 停止
systemctl stop proxysql
# 重启
systemctl restart proxysql
# 重新初始化:强制 ProxySQL 忽略已持久化的 SQLite 数据库,重新从原始配置文件(proxysql.cnf)加载所有配置并重置数据库。
systemctl start proxysql-initial
# 查看版本
proxysql --version
ProxySQL 有守护进程机制,正常 stop 有时无法退出,直接 kill -9 是最稳妥的关闭方式。
(2)使用 ProxySQL 管理接口
$mysql -u admin -padmin -h 127.0.0.1 -P6032 --prompt='Admin> '
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.5.30 (ProxySQL Admin Module)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
Admin> show databases;
+-----+---------------+-------------------------------------+
| seq | name | file |
+-----+---------------+-------------------------------------+
| 0 | main | |
| 2 | disk | /var/lib/proxysql/proxysql.db |
| 3 | stats | |
| 4 | monitor | |
| 5 | stats_history | /var/lib/proxysql/proxysql_stats.db |
+-----+---------------+-------------------------------------+
5 rows in set (0.00 sec)
- main:活跃的内存配置,包括后端服务器、用户、查询规则、变量等,修改后不会立即生效。
- disk:持久化配置,写入到 SQLite 数据库文件中。
- stats:运行时统计信息,如连接数、SQL 摘要、内存使用等。
- monitor:后端服务器的健康检查结果。
- stats_history:历史统计数据,写入到 SQLite 数据库文件中。
修改内存配置后,需将其加载到运行时,并可选择持久化到磁盘:
-- 使配置立即生效
LOAD MYSQL SERVERS TO RUNTIME;
-- 使配置永久保存
SAVE MYSQL SERVERS TO DISK;
在管理接口中可以启停、重启 ProxySQL:
Admin> proxysql stop;
Admin> proxysql start;
Admin> proxysql restart;
4. 配置文件
/etc/proxysql.cnf 是 ProxySQL 的核心启动配置文件,仅用于首次初始化/全新实例启动。ProxySQL 第一次启动后,内置 SQLite 数据库优先级更高。后续修改配置,推荐通过 MySQL 客户端连接 ProxySQL 管理接口修改,配置会持久化到数据库。不建议直接改配置文件。
(1)admin_variables
管理接口配置,控制连接 ProxySQL 自身的管理后台。
admin_variables=
{
admin_credentials="admin:admin" # 管理账号:密码
mysql_ifaces="127.0.0.1:6032" # MySQL 协议管理地址(默认本地 6032 端口)
pgsql_ifaces="127.0.0.1:6132" # PostgreSQL 协议管理地址(默认本地 6132 端口)
}
(2)mysql_variables
MySQL 流量处理配置,控制 ProxySQL 接收 MySQL 客户端连接的核心参数。
mysql_variables=
{
threads=4 # 工作线程数(CPU 核心数匹配最佳)
max_connections=2048 # 代理最大连接数
interfaces="0.0.0.0:6033" # 监听所有IP,6033 端口(客户端连接端口)
}
(3)mysql_servers
后端真实 MySQL 服务器列表,定义 ProxySQL 转发流量的目标数据库节点。
mysql_servers=
(
{
address="127.0.0.1" # 后端MySQL IP
port=3306 # 后端MySQL端口
hostgroup=0 # 所属主机组(0=主库/写库)
max_connections=200 # 代理到该节点的最大连接数
}
)
(4)mysql_users
允许通过代理连接的用户,定义应用程序/客户端连接 ProxySQL 使用的账号密码。
mysql_users=
(
{
username="root" # 连接用户名
password="root" # 连接密码
default_hostgroup=0 # 默认路由到主机组 0
max_connections=1000 # 该用户最大连接数
default_schema="information_schema" # 默认数据库
active=1 # 启用该用户
}
)
(5)mysql_query_rules
SQL 流量路由规则(核心功能),自动根据 SQL 语句转发到不同主机组(读写分离核心配置)。匹配模式不区分大小写。
mysql_query_rules=
(
# 规则1:带 FOR UPDATE 的查询 → 主机组 0(写库/主库)
{
rule_id=1
active=1
match_pattern="^SELECT .* FOR UPDATE$"
destination_hostgroup=0
apply=1
},
# 规则2:普通 SELECT 查询 → 主机组 1(读库/从库)
{
rule_id=2
active=1
match_pattern="^SELECT"
destination_hostgroup=1
apply=1
}
)
(6)datadir
数据存储目录,ProxySQL 存放 SQLite 数据库、日志、缓存的目录。
datadir="/var/lib/proxysql"
三、配置 ProxySQL 对接 MySQL
假设 ProxySQL 已安装并正常运行。开始前先连接 ProxySQL 管理界面:
mysql -u admin -padmin -h 127.0.0.1 -P6032 --prompt 'ProxySQL Admin> '
1. 环境说明
MySQL 版本 8.0.22,标准的一主两从异步复制,使用 GTID 的 Auto_Position 自动定位复制位点。
- 主库:172.18.3.122:18251
- 从库:172.18.3.232:18251、172.18.4.109:18251
主库的 read_only、super_read_only 为 off,从库为 on。目标是使用 ProxySQL 自动进行读写分离,将写请求发送到主库,读请求发送到主库和从库,并且将读请求在一主两从三个实例上做负载均衡。
2. 配置步骤
(1)添加后端服务器
向 mysql_servers 表中添加 MySQL 服务器,需指定主机组、主机名和端口:
sql
INSERT INTO mysql_servers(hostgroup_id,hostname,port) VALUES (1,'172.18.3.122',18251);
INSERT INTO mysql_servers(hostgroup_id,hostname,port) VALUES (1,'172.18.3.232',18251);
INSERT INTO mysql_servers(hostgroup_id,hostname,port) VALUES (1,'172.18.4.109',18251);
(2)配置监控
在 MySQL 中创建专用监控用户:
sql
-- 主库执行
CREATE USER monitor IDENTIFIED BY 'monitor';
GRANT USAGE, REPLICATION CLIENT ON *.* TO monitor;
在 ProxySQL 中配置监控的用户名、密码和监控间隔,将以下三项监控间隔统一设置为 2000 毫秒(2 秒):
- 连接监控间隔
- 心跳检测间隔
- 只读状态检查间隔
sql
UPDATE global_variables SET variable_value='monitor'
WHERE variable_name='mysql-monitor_username';
UPDATE global_variables SET variable_value='monitor'
WHERE variable_name='mysql-monitor_password';
UPDATE global_variables SET variable_value='2000'
WHERE variable_name IN (
'mysql-monitor_connect_interval',
'mysql-monitor_ping_interval',
'mysql-monitor_read_only_interval'
);
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;
加载服务器配置到运行时:
sql
LOAD MYSQL SERVERS TO RUNTIME;
通过 monitor 库查看后端服务器健康状态:
sql
ProxySQL Admin> SELECT * FROM monitor.mysql_server_connect_log ORDER BY time_start_us DESC LIMIT 3;
+--------------+-------+------------------+-------------------------+---------------+
| hostname | port | time_start_us | connect_success_time_us | connect_error |
+--------------+-------+------------------+-------------------------+---------------+
| 172.18.4.109 | 18251 | 1776662617024619 | 1190 | NULL |
| 172.18.3.122 | 18251 | 1776662617004344 | 941 | NULL |
| 172.18.3.232 | 18251 | 1776662616984052 | 903 | NULL |
+--------------+-------+------------------+-------------------------+---------------+
3 rows in set (0.00 sec)
ProxySQL Admin> SELECT * FROM monitor.mysql_server_ping_log ORDER BY time_start_us DESC LIMIT 3;
+--------------+-------+------------------+----------------------+------------+
| hostname | port | time_start_us | ping_success_time_us | ping_error |
+--------------+-------+------------------+----------------------+------------+
| 172.18.3.122 | 18251 | 1776662622808567 | 98 | NULL |
| 172.18.4.109 | 18251 | 1776662622808445 | 265 | NULL |
| 172.18.3.232 | 18251 | 1776662622808393 | 307 | NULL |
+--------------+-------+------------------+----------------------+------------+
3 rows in set (0.00 sec)
(3)配置复制主机组
通过映射写入主机组和读取主机组,配置复制拓扑监控:
sql
INSERT INTO mysql_replication_hostgroups (writer_hostgroup,reader_hostgroup,comment)
VALUES (1,2,'cluster1');
-- 手动把主库加入读组 2(永久生效,不会被自动模式覆盖)
INSERT INTO mysql_servers (hostgroup_id, hostname, port)
VALUES (2, '172.18.3.122', 18251);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
ProxySQL 会根据 MySQL 的 read_only 参数,自动在读、写主机组之间迁移节点:
- read_only = 0 → 归属主机组 1(写入节点/主库)
- read_only = 1 → 归属主机组 2(读取节点/从库)
验证:
sql
ProxySQL Admin> SELECT hostgroup_id, hostname, port, status
-> FROM runtime_mysql_servers
-> ORDER BY hostname, hostgroup_id;
+--------------+--------------+-------+--------+
| hostgroup_id | hostname | port | status |
+--------------+--------------+-------+--------+
| 1 | 172.18.3.122 | 18251 | ONLINE |
| 2 | 172.18.3.122 | 18251 | ONLINE |
| 2 | 172.18.3.232 | 18251 | ONLINE |
| 2 | 172.18.4.109 | 18251 | ONLINE |
+--------------+--------------+-------+--------+
4 rows in set (0.01 sec)
从查询结果可以看到,写请求只走主库(组 1),读请求 3 台一起承担(主 + 从 + 从)。
(4)添加 MySQL 用户
将 MySQL 用户添加到 ProxySQL 的 mysql_users 表中:
sql
INSERT INTO mysql_users(username,password,default_hostgroup)
VALUES ('wxy','123456',1);
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
(5)配置查询规则
sql
UPDATE mysql_users SET default_hostgroup=1;
LOAD MYSQL USERS TO RUNTIME;
-- 插入生产级完整规则
INSERT INTO mysql_query_rules (rule_id,active,match_digest,destination_hostgroup,apply)
VALUES
-- 所有写操作 DML
(1,1,'^INSERT',1,1),
(2,1,'^UPDATE',1,1),
(3,1,'^DELETE',1,1),
(4,1,'^REPLACE',1,1),
(5,1,'^MERGE',1,1),
-- 所有 DDL 操作(你漏的这些!)
(10,1,'^CREATE',1,1),
(11,1,'^ALTER',1,1),
(12,1,'^DROP',1,1),
(13,1,'^TRUNCATE',1,1),
(14,1,'^RENAME',1,1),
(15,1,'^GRANT',1,1),
(16,1,'^REVOKE',1,1),
-- 锁查询强制主库
(20,1,'^SELECT.*FOR UPDATE',1,1),
(21,1,'^SELECT.*LOCK IN SHARE MODE',1,1),
-- 普通查询走读组 3台负载
(100,1,'^SELECT',2,1);
-- 加载并保存
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
-
规则按照 rule_id 从小到大 依次匹配。
-
只有 active=1 的规则才会生效。
-
一旦匹配到 apply=1 的规则,立刻停止匹配后续规则。
-
如果没有任何规则匹配,就使用 default_hostgroup(用户默认组)。
-
显式开启的的事务,由于没有匹配任何规则,走写组。而且事务中的后续语句都会固定在写组,直到事务结束,这是由 transaction_persistent 决定的。
sqlProxySQL Admin> SELECT username, transaction_persistent FROM mysql_users; +----------+------------------------+ | username | transaction_persistent | +----------+------------------------+ | wxy | 1 | +----------+------------------------+ 1 row in set (0.00 sec)
(6)查询缓存
在匹配的规则上设置 cache_ttl(单位:毫秒)来开启查询缓存:
sql
UPDATE mysql_query_rules SET cache_ttl=5000
WHERE active=1 AND destination_hostgroup=2;
LOAD MYSQL QUERY RULES TO RUNTIME;
(8)保存配置
sql
LOAD MYSQL USERS TO RUNTIME;
LOAD MYSQL SERVERS TO RUNTIME;
LOAD MYSQL QUERY RULES TO RUNTIME;
LOAD MYSQL VARIABLES TO RUNTIME;
LOAD ADMIN VARIABLES TO RUNTIME;
SAVE MYSQL USERS TO DISK;
SAVE MYSQL SERVERS TO DISK;
SAVE MYSQL QUERY RULES TO DISK;
SAVE MYSQL VARIABLES TO DISK;
SAVE ADMIN VARIABLES TO DISK;
ProxySQL 任何配置:用户、路由、后端节点、延迟阈值、规则... 全部在线热加载,不用重启 proxysql 服务,不用中断业务连接,不影响正在运行的 SQL。
ProxySQL 把配置分成三层,执行命令就是在层之间搬运:
- MEMORY → 当前改的配置(内存里)
- RUNTIME → 正在运行、生效的配置
- DISK → 持久化到磁盘,重启不丢
不管改了什么,永远用这两句生效 + 持久化:
sql
LOAD xxx TO RUNTIME; --> 立即在线生效(不重启)
SAVE xxx TO DISK; --> 持久化到磁盘(重启不丢失)
四、测试与验证
在 ProxySQL 所在机器执行:
bash
mysql -uwxy -p123456 -h127.0.0.1 -P6033
这是业务入口,所有测试都走这里。
同时打开 ProxySQL 管理窗口:
bash
mysql -uadmin -padmin -h127.0.0.1 -P6032
1. 读写分离
sql
-- 写请求必须走主库(hostgroup 1)
CREATE DATABASE test_proxysql;
USE test_proxysql;
CREATE TABLE t(id INT);
INSERT INTO t VALUES (1);
UPDATE t SET id=2;
DELETE FROM t WHERE id=2;
在管理端查看流量分布:
sql
ProxySQL Admin> SELECT
-> hostgroup,
-> count_star,
-> last_seen,
-> digest_text
-> FROM stats_mysql_query_digest
-> ORDER BY last_seen DESC;
+-----------+------------+------------+----------------------------------+
| hostgroup | count_star | last_seen | digest_text |
+-----------+------------+------------+----------------------------------+
| 1 | 1 | 1776668947 | DELETE FROM t WHERE id=? |
| 1 | 1 | 1776668947 | INSERT INTO t VALUES (?) |
| 1 | 1 | 1776668947 | show databases |
| 1 | 1 | 1776668947 | CREATE TABLE t(id INT) |
| 1 | 1 | 1776668947 | CREATE DATABASE test_proxysql |
| 2 | 1 | 1776668947 | SELECT DATABASE() |
| 1 | 1 | 1776668947 | show tables |
| 1 | 1 | 1776668947 | UPDATE t SET id=? |
| 1 | 1 | 1776668879 | show variables like ? |
| 1 | 1 | 1776668865 | show databases |
| 1 | 1 | 1776668860 | select @@version_comment limit ? |
+-----------+------------+------------+----------------------------------+
11 rows in set (0.01 sec)
各 SQL 语句说明如下:
bash
+-----------+----------------------------------+
| hostgroup | digest_text | 是否手动执行?
+-----------+----------------------------------+
| 1 | DELETE FROM t WHERE id=? | ✅ 手动
| 1 | INSERT INTO t VALUES (?) | ✅ 手动
| 1 | show databases | ❌ 自动
| 1 | CREATE TABLE t(id INT) | ✅ 手动
| 1 | CREATE DATABASE test_proxysql | ✅ 手动
| 2 | SELECT DATABASE() | ✅ 手动(USE 指令)
| 1 | show tables | ❌ 自动
| 1 | UPDATE t SET id=? | ✅ 手动
读写分离 100% 正确,手动执行的所有写操作,全部 → hostgroup=1(主库);手动执行的读操作:SELECT DATABASE() → hostgroup=2(读组)。
- 执行的 6 条全部查到了。
- 多出来的是系统自动 SQL,完全正常。
- 读写分离正常工作。
2. 读负载均衡
bash
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 10918251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 10918251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 10918251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$mysql -uwxy -p123456 -h127.0.0.1 -P6033 -e "SELECT @@server_id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@server_id |
+-------------+
| 12218251 |
+-------------+
[mysql@kxvv-yz-mysqlproxy~]$
读组里 3 个节点,但 ProxySQL 默认会优先选 "延迟最低、响应最快" 的节点!现在的结果是:
- 10918251 → 出现几次
- 12218251 → 霸占几乎所有请求
- 23218251 → 几乎不出现
为什么手动单 IP 测试,看不到轮询?即使在权重均为 1 的情况下,ProxySQL 也无法实现严格的"1-2-3-1-2-3..."轮询顺序。这并非配置错误,而是由ProxySQL内部连接处理机制决定的。以下是官方文档和架构层面的解释。
- 负载均衡是基于连接的
ProxySQL 的轮询算法在决策时,基本单位是"连接"而不是"单个查询"。它的工作流程是:
- 客户端与 ProxySQL 建立一个连接。
- ProxySQL 根据当前配置的负载均衡算法(如轮询、最小连接数等),从后端服务器池中选择一个服务器。
- 该客户端连接的整个会话期间,除非发生特定故障转移,否则其所有查询都会被发送到同一个选定的后端服务器。
这意味着,如果通过单个 IP 连续发起多次短连接(每次查询都新建连接并断开),每次新建连接时 ProxySQL 都会执行一次轮询调度。但这只能保证"连接次数"是轮询的,无法保证"单个连接内的查询顺序"是轮询的。
- 连接池会复用后端连接
ProxySQL 为每个后端服务器维护一个连接池。当客户端断开连接时,其使用的后端连接可能不会立即关闭,而是被放回连接池以供复用。这进一步增加了轮询行为的不确定性。
- 调度粒度:线程级别
ProxySQL 内部的多线程架构也影响了调度顺序。多个工作线程各自独立地分发请求,而不是由一个全局调度器统一控制。在高并发场景下,线程间的调度竞争会打破理想的严格轮询顺序。
严格轮询(如1-2-3-1-2-3)是有状态调度,调度器需要记住上一次的选择结果。而 ProxySQL 的设计目标是高性能和无状态转发。在数千并发连接下维护全局的、严格的轮询序列会带来巨大的锁竞争和状态同步开销,这与 ProxySQL 的高性能代理定位相悖。
通过单 IP 多次建立短连接(例如使用 mysql -h proxysql -e "SELECT 1" 循环100次),观察到的分布通常是:
- 加权轮询分布:如果所有节点权重相同(weight=1),长期来看,三个节点接收的连接数会趋近于相等(比如 33、33、34)。
- 不是严格的 1-2-3 序列:所看到的顺序可能是1、1、2、3、2、1、3......等随机排列。
ProxySQL 官方文档强调,其负载均衡是统计意义上的均衡,而不是严格的顺序轮询。它保证在大量请求下各节点负载相近,但不承诺请求顺序。
本例看到的现象是 ProxySQL 2.7.3 标准行为。要想展现负载均衡,须要满足:多IP、高并发、长连接大量请求。这样流量会自动均匀分散到 3 台机器。
五、数据一致性问题
本例中的规则配置中,将所有普通 select 查询都走读组的三个实例。这里有个问题,MySQL 缺省的异步复制不保证主从数据的强一致性,因为从库可能存在复制延迟。这种情况下,当用 ProxySQL 做读写分离时,也就无法保证读取数据的强一致性。这不是 ProxySQL 的问题,而是 MySQL 异步复制所决定的。
1. 开启半同步复制
最简单的解决方案是,将异步复制改为半同步复制,在 MySQL 层面保证主从数据的强一致性。这种方案对 ProxySQL 和应用完全透明。
配置半同步复制的步骤如下,考虑到可能的主从切换,配置在所有主从实例都执行:
- 安装半同步插件
sql
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
- 在线开启半同步
sql
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 1000; -- 超时1秒后退为异步
SET GLOBAL rpl_semi_sync_slave_enabled = 1;
- 从库重启复制
sql
STOP SLAVE;
START SLAVE;
- 验证是否开启
sql
SHOW STATUS LIKE 'Rpl_semi_sync%';
想永久生效,要把配置写到配置文件中:
bash
# 主库
plugin_load_add = semisync_master.so
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 1000
# 从库
plugin_load_add = semisync_slave.so
rpl_semi_sync_slave_enabled = 1
2. 修改查询规则
主要有以下两种方法,通过定制 ProxySQL 的查询规则将要求强一致性的读查询路由到主库。
(1)基于 SQL 语句分析的智能路由
这是 ProxySQL 推荐的方法,配置步骤为:
- 初始阶段将所有流量路由到主库。
- 分析 stats_mysql_query_digest 识别高开销的 SELECT 语句。
- 确定哪些语句可以安全地在从库上运行。
- 添加针对性的路由规则。
参见:https://www.proxysql.com/documentation/proxysql-read-write-split-howto
(2)将需要强一致性的 select 路由到主库
例如执行以下步骤,将制定查询路由到主库:
- 业务代码里这样写 SQL(加固定注释):
sql
/* FORCE_MASTER */ SELECT * FROM user WHERE id = 1;
/* FORCE_MASTER */ SELECT balance FROM account WHERE uid = 100;
只要带 /* FORCE_MASTER */ 这个注释,强制走主库。
- ProxySQL 只加 1 条规则(优先级高于普通 select 查询)
sql
-- 插入规则:匹配带 /* FORCE_MASTER */ 的查询,强制路由主库
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (30, 1, '/\* FORCE_MASTER \*/', 1, 1);
-- 加载生效
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;