ProxySQL(一)—— 实现 MySQL 读写分离、读负载均衡

目录

[一、ProxySQL 简介](#一、ProxySQL 简介)

[1. 核心功能](#1. 核心功能)

(1)读写分离(最常用)

(2)负载均衡(读库横向扩展)

(3)连接池管理(解决连接风暴)

(4)自动故障转移(高可用)

(5)查询路由(灵活规则)

(6)查询缓存(性能加速)

(7)监控、限流与审计

(8)动态配置(不停机运维)

[2. 什么时候用 ProxySQL](#2. 什么时候用 ProxySQL)

[(1)主从架构 + 读写分离(最典型)](#(1)主从架构 + 读写分离(最典型))

[(2)MySQL 集群(MGR/PXC/InnoDB Cluster)](#(2)MySQL 集群(MGR/PXC/InnoDB Cluster))

[(3)高并发、大流量(互联网 / 金融 / 电商)](#(3)高并发、大流量(互联网 / 金融 / 电商))

(4)多租户/混合业务

(5)简化运维、统一管理

(6)云数据库(RDS/Aurora)

二、安装配置

[1. 版本说明](#1. 版本说明)

[2. 下载与安装](#2. 下载与安装)

[3. 服务与管理接口](#3. 服务与管理接口)

[(1)使用 systemctl](#(1)使用 systemctl)

[(2)使用 ProxySQL 管理接口](#(2)使用 ProxySQL 管理接口)

[4. 配置文件](#4. 配置文件)

(1)admin_variables

(2)mysql_variables

(3)mysql_servers

(4)mysql_users

(5)mysql_query_rules

(6)datadir

[三、配置 ProxySQL 对接 MySQL](#三、配置 ProxySQL 对接 MySQL)

[1. 环境说明](#1. 环境说明)

[2. 配置步骤](#2. 配置步骤)

(1)添加后端服务器

(2)配置监控

(3)配置复制主机组

[(4)添加 MySQL 用户](#(4)添加 MySQL 用户)

(5)配置查询规则

(6)查询缓存

(8)保存配置

四、测试与验证

[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。

版本系列说明:

  • 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 决定的。

    sql 复制代码
    ProxySQL 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 的轮询算法在决策时,基本单位是"连接"而不是"单个查询"。它的工作流程是:

  1. 客户端与 ProxySQL 建立一个连接。
  2. ProxySQL 根据当前配置的负载均衡算法(如轮询、最小连接数等),从后端服务器池中选择一个服务器。
  3. 该客户端连接的整个会话期间,除非发生特定故障转移,否则其所有查询都会被发送到同一个选定的后端服务器。

这意味着,如果通过单个 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;
相关推荐
源远流长jerry1 天前
LVS 负载均衡完全指南:从入门到精通
运维·负载均衡·lvs
hINs IONN2 天前
RabbitMQ HAProxy 负载均衡
rabbitmq·负载均衡·ruby
llm大模型算法工程师weng3 天前
负载均衡做什么?nginx是什么
运维·开发语言·nginx·负载均衡
Gofarlic_oms14 天前
制定企业Citrix虚拟化软件资产管理政策框架
运维·服务器·开发语言·matlab·负载均衡
随风,奔跑4 天前
SpringCloudAlibaba(二)
java·spring·ribbon·负载均衡
武超杰4 天前
Ribbon 负载均衡 + Feign 声明式调用 从入门到实战
spring cloud·ribbon·负载均衡
小旭95274 天前
Spring Cloud Ribbon 与 Feign 实战:负载均衡与声明式服务调用
spring cloud·ribbon·负载均衡
StackNoOverflow4 天前
SpringCloud的负载均衡
spring cloud·ribbon·负载均衡
博风4 天前
nginx:负载均衡
运维·nginx·负载均衡