ProxySQL(二)—— 实现 MySQL 主从自动失败切换

目录

一、环境

[1. 节点信息](#1. 节点信息)

[2. HostGroup 规划](#2. HostGroup 规划)

[3. 要实现的能力](#3. 要实现的能力)

二、配置

[1. ProxySQL 健康检查 & 自动上下线配置](#1. ProxySQL 健康检查 & 自动上下线配置)

(1)检查当前配置

(2)设置最大延迟

[2. 自动故障切换脚本](#2. 自动故障切换脚本)

[3. 使用 crontab 调度执行](#3. 使用 crontab 调度执行)

三、测试

[1. 从库故障 → 自动下线 → 恢复自动上线](#1. 从库故障 → 自动下线 → 恢复自动上线)

(1)查看当前状态

(2)模拟故障

(3)查看自动下线

(4)恢复并查看自动上线

[2. 主库宕机自动切换](#2. 主库宕机自动切换)

(1)查看当前状态

(2)模拟故障

(3)查看自动下线

(4)确认状态

(3)读写数据

(4)将下线的老主库重新上线

[3. 业务无感知验证](#3. 业务无感知验证)

(1)准备测试表(主库执行)

(2)模拟业务持续读写脚本

(3)模拟主库宕机(触发自动切换)

(4)查询业务表

(5)恢复宕机实例


一、环境

已经用 ProxySQL 对 MySQL 做了读写分离和读负载均衡:

  • MySQL 8.0.22,半同步复制,一主两从,Auto_Position 自动定位。
  • ProxySQL 2.7.3,写走主库(单实例),读走主库+从库(三实例负载均衡)

参见:ProxySQL(一)------ 实现 MySQL 读写分离、读负载均衡

1. 节点信息

  • 主库:172.18.3.122:18251
  • 从库1:172.18.3.232:18251
  • 从库2:172.18.4.109:18251
  • ProxySQL:2.7.3(Admin 6032,业务端口 6033)

2. HostGroup 规划

  • hostgroup 1:写流量(只指向主库)
  • hostgroup 2:读流量(主 + 从1 + 从2 负载均衡)

3. 要实现的能力

  • 实时健康检查(ping + 连通性)。
  • 从库故障自动下线。
  • 从库恢复自动上线。
  • 主库宕机自动选最新从库提升。
  • 剩余从库自动指向新主。
  • 自动更新 ProxySQL 配置。
  • 全程无人工干预、业务无感知。

二、配置

1. ProxySQL 健康检查 & 自动上下线配置

(1)检查当前配置

与健康检查 & 自动上下线配置相关的主要配置参数如下:

sql 复制代码
ProxySQL Admin> select * from global_variables where variable_name in (
    ->      'mysql-monitor_enabled',
    ->      'mysql-monitor_ping_interval',
    ->      'mysql-monitor_ping_max_failures',
    ->      'mysql-monitor_connect_timeout',
    ->      'mysql-monitor_username',
    ->      'mysql-monitor_password'
    ->      );
+---------------------------------+----------------+
| variable_name                   | variable_value |
+---------------------------------+----------------+
| mysql-monitor_connect_timeout   | 600            |
| mysql-monitor_enabled           | 1              |
| mysql-monitor_password          | monitor        |
| mysql-monitor_ping_interval     | 2000           |
| mysql-monitor_ping_max_failures | 3              |
| mysql-monitor_username          | monitor        |
+---------------------------------+----------------+
6 rows in set (0.00 sec)
  • mysql-monitor_connect_timeout

单位:毫秒(ms)。

含义:ProxySQL 监控后端 MySQL 时,连接超时时间。

解释:尝试连接后端 MySQL,如果 600ms 连不上,就判定这次连接检查失败。

作用:快速识别无法建立连接的坏节点。

  • mysql-monitor_enabled

含义:是否启用 ProxySQL 内置的 MySQL 监控模块,默认启用。

作用:打开自动探活、自动剔除坏节点、自动恢复节点。

  • mysql-monitor_username、mysql-monitor_password

含义:ProxySQL 用来监控后端 MySQL 的专用账号密码。

作用:探活(ping)、检查只读、检查复制延迟、检查连接可用性。

注意:这个账号必须在所有后端 MySQL 上存在,只需要 REPLICATION CLIENT 权限。

  • mysql-monitor_ping_interval

单位:毫秒(ms)

含义:每隔多久对后端 MySQL 发起一次 ping 探活检查,2000ms = 2秒。

作用:2 秒检查一次后端是否活着。

  • mysql-monitor_ping_max_failures

含义:连续 ping 失败多少次,就把后端节点标记为 SHUNNED(下线/屏蔽)。

作用:连续失败 3 次,也就是 2秒 × 3次 = 6秒 节点无响应则自动下线。

(2)设置最大延迟

为防止因为复制延迟而造成从库读到的数据过旧,设置最大延迟为 60 秒:

sql 复制代码
-- 给所有读节点设置最大延迟 60 秒
update mysql_servers set max_replication_lag=60;
-- 加载到运行时
load mysql servers to runtime;
-- 持久化保存
save mysql servers to disk;

ProxySQL 目前工作模式:

  • 监控已启用,每 2 秒探活一次,600ms 连接超时;
  • 连续 3 次探活失败 → 自动摘除节点(SHUNNED);
  • 复制延迟 > 60 秒 → 自动临时摘除(SHUNNED);
  • 故障恢复后自动重新加入集群;
  • 只要探测成功一次,立刻恢复为 ONLINE,立刻重新加入集群。

2. 自动故障切换脚本

脚本文件 proxysql_mysql_ha.sh 内容如下:

bash 复制代码
#!/bin/bash
source ~/.bash_profile

# ==================== 配置项 ====================
MYSQL_USER="wxy"
MYSQL_PASS="123456"

PROXYSQL_ADMIN_HOST="127.0.0.1"
PROXYSQL_ADMIN_PORT="6032"
PROXYSQL_ADMIN_USER="admin"
PROXYSQL_ADMIN_PASS="admin"

MASTER_HG=1
SLAVE_HG=2
LOG_FILE="/home/mysql/proxysql_ha.log"

REPL_USER="wxy"
REPL_PASS="123456"
# ======================================================

log() {
  echo "[$(date +%Y-%m-%d_%H:%M:%S)] $*" >> "${LOG_FILE}"
}

# 获取当前主库
get_runtime_master() {
  mysql -h"${PROXYSQL_ADMIN_HOST}" -P"${PROXYSQL_ADMIN_PORT}" \
    -u"${PROXYSQL_ADMIN_USER}" -p"${PROXYSQL_ADMIN_PASS}" -Nse \
    "SELECT hostname,port FROM runtime_mysql_servers
     WHERE hostgroup_id=${MASTER_HG}
     ORDER BY CASE WHEN status = 'ONLINE' THEN 1 ELSE 2 END
     LIMIT 1" 2>/dev/null
}

# 检测存活
is_alive() {
  local host="$1" port="$2"
  mysql -h"$host" -P"$port" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" \
    -Nse "SELECT 1" >/dev/null 2>&1
}

# 获取GTID (MySQL 8.0 正确)
get_gtid() {
  local host="$1" port="$2"
  mysql -h"$host" -P"$port" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" \
    -Nse "SELECT @@GLOBAL.gtid_executed" 2>/dev/null
}

# 提升新主
promote() {
  local h="$1" p="$2"
  log "提升新主:$h:$p"
  mysql -h"$h" -P"$p" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" -e "
STOP SLAVE;
RESET SLAVE ALL;
SET PERSIST read_only=OFF;
SET PERSIST super_read_only=OFF;
" 2>/dev/null
}

# 从库指向新主
change_master() {
  local s_h="$1" s_p="$2" m_h="$3" m_p="$4"
  log "从库 $s_h:$s_p 指向新主 $m_h:$m_p"
  mysql -h"$s_h" -P"$s_p" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" -e "
STOP SLAVE;
CHANGE MASTER TO
MASTER_HOST='$m_h',
MASTER_PORT=$m_p,
MASTER_USER='$REPL_USER',
MASTER_PASSWORD='$REPL_PASS',
MASTER_AUTO_POSITION=1;
START SLAVE;
" 2>/dev/null
}

# ==================== 主逻辑 ====================
log "=== 检查开始 ==="

master_info=$(get_runtime_master)
m_host=$(echo "$master_info" | awk '{print $1}')
m_port=$(echo "$master_info" | awk '{print $2}')

if [ -z "$m_host" ]; then
  log "ERROR:无主库"
  exit 1
fi

log "当前主库:$m_host:$m_port"

# 主库正常,直接退出
if is_alive "$m_host" "$m_port"; then
  log "主库正常"
  exit 0
fi

log "WARN:主库宕机,开始自动切换"

# 获取所有在线从库
slaves=$(mysql -h"${PROXYSQL_ADMIN_HOST}" -P"${PROXYSQL_ADMIN_PORT}" \
  -u"${PROXYSQL_ADMIN_USER}" -p"${PROXYSQL_ADMIN_PASS}" -Nse \
  "SELECT hostname,port FROM runtime_mysql_servers
   WHERE hostgroup_id=${SLAVE_HG} AND status='ONLINE'" 2>/dev/null)

if [ -z "$slaves" ]; then
  log "ERROR:无可用从库"
  exit 1
fi

# GTID 选最新
best_gtid=""
best_host=""
best_port=""

while read -r h p; do
  [ -z "$h" ] && continue
  curr_gtid=$(get_gtid "$h" "$p")
  log "从库 $h:$p GTID:$curr_gtid"

  if [ -z "$best_gtid" ]; then
    best_gtid="$curr_gtid"
    best_host="$h"
    best_port="$p"
  else
    # 如果 best_gtid 是 curr_gtid 的子集,说明 best_gtid 更旧,需要更新
    if mysql -Nse "SELECT GTID_SUBSET('$best_gtid','$curr_gtid')" 2>/dev/null; then
      best_gtid="$curr_gtid"
      best_host="$h"
      best_port="$p"
    fi
  fi
done <<< "$slaves"

log "最优新主:$best_host:$best_port"

# 1. 提升
promote "$best_host" "$best_port"

# 2. 其他从库重定向
while read -r h p; do
  [ -z "$h" ] || [ "$h" = "$best_host" ] && continue
  change_master "$h" "$p" "$best_host" "$best_port"
done <<< "$slaves"

log "=== 全部完成!==="

3. 使用 crontab 调度执行

bash 复制代码
* * * * * /home/mysql/proxysql_mysql_ha.sh

不要用下面的方法使用 ProxySQL 的 scheduler 进行调度:

sql 复制代码
insert into scheduler (id, active, interval_ms, filename, comment)
values (1, 1, 2000, '/home/mysql/proxysql_mysql_ha.sh', 'mysql 自动主从切换');

load scheduler to runtime;
save scheduler to disk;

ProxySQL 的 Scheduler 是受限隔离环境,并非完整系统环境,会干扰/劫持外部网络连接,无法稳定、真实地检测后端 MySQL Server 存活,不适合做主从高可用切换。

三、测试

1. 从库故障 → 自动下线 → 恢复自动上线

(1)查看当前状态

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+--------+---------------------+
| hostgroup_id | hostname     | status | max_replication_lag |
+--------------+--------------+--------+---------------------+
| 1            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.4.109 | ONLINE | 60                  |
+--------------+--------------+--------+---------------------+
4 rows in set (0.00 sec)

(2)模拟故障

bash 复制代码
# 在 172.18.3.232 执行
kill -9 $(ps -ef | grep 18251 | grep -v grep | awk '{print $2}')
ps -ef | grep 18251

(3)查看自动下线

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+---------+---------------------+
| hostgroup_id | hostname     | status  | max_replication_lag |
+--------------+--------------+---------+---------------------+
| 1            | 172.18.3.122 | ONLINE  | 60                  |
| 2            | 172.18.3.122 | ONLINE  | 60                  |
| 2            | 172.18.3.232 | SHUNNED | 60                  |
| 2            | 172.18.4.109 | ONLINE  | 60                  |
+--------------+--------------+---------+---------------------+
4 rows in set (0.00 sec)

(4)恢复并查看自动上线

bash 复制代码
# 在 172.18.3.232 执行
mysqld_safe --defaults-file=/home/mysql/my_18251.cnf &

等待 2~4 秒:

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+---------+---------------------+
| hostgroup_id | hostname     | status  | max_replication_lag |
+--------------+--------------+---------+---------------------+
| 1            | 172.18.3.122 | ONLINE  | 60                  |
| 2            | 172.18.3.122 | ONLINE  | 60                  |
| 2            | 172.18.3.232 | SHUNNED | 60                  |
| 2            | 172.18.4.109 | ONLINE  | 60                  |
+--------------+--------------+---------+---------------------+
4 rows in set (0.00 sec)

没有自动上线。打开一个新终端,连接 ProxySQL 的 6033 业务端口:

bash 复制代码
mysql -u wxy -p -h 127.0.0.1 -P6033

然后随便执行一条读命令,让它走 HG2 读组:

sql 复制代码
mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|    23218251 |
+-------------+
1 row in set (0.00 sec)

回到 ProxySQL Admin 查看:

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+--------+---------------------+
| hostgroup_id | hostname     | status | max_replication_lag |
+--------------+--------------+--------+---------------------+
| 1            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.4.109 | ONLINE | 60                  |
+--------------+--------------+--------+---------------------+
4 rows in set (0.00 sec)

官方文档明确说明:SHUNNED 只有一种自动恢复触发方式:必须有新流量/连接池活动。下面直接贴 ProxySQL 2.7.3 官方原文(FAQ):

bash 复制代码
ProxySQL is quite unique when it performs failure detection.It can detect that a server is down not because its Monitoring system fails to check the status of a backend, but because queries are failing.

The core of ProxySQL works this way (even without Monitor enabled):
 1. Initially all nodes are ONLINE.
 2. When sending traffic to a node errors are generated,
    if more than mysql-shun_on_failures errors in 1s → node is SHUNNED
    for mysql-shun_recovery_time_sec seconds.
 3. After mysql-shun_recovery_time_sec,
    ProxySQL will bring the node back online
    only if there is activity in the connection pool for that hostgroup.
 4. If the node is still down, it will be SHUNNED again.

One important thing to note:ProxySQL does NOT have a timer to auto-unshun.The node is brought back online only on demand / when traffic arrives.

重点是:2.7 版本的 ProxySQL 没有定时器自动解除 SHUNNED,节点恢复为 ONLINE 唯一条件是有流量请求、按需恢复。

2. 主库宕机自动切换

(1)查看当前状态

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+--------+---------------------+
| hostgroup_id | hostname     | status | max_replication_lag |
+--------------+--------------+--------+---------------------+
| 1            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.4.109 | ONLINE | 60                  |
+--------------+--------------+--------+---------------------+
4 rows in set (0.00 sec)

(2)模拟故障

bash 复制代码
# 在 172.18.3.122 执行
kill -9 $(ps -ef | grep 18251 | grep -v grep | awk '{print $2}')
ps -ef | grep 18251

(3)查看自动下线

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+---------+---------------------+
| hostgroup_id | hostname     | status  | max_replication_lag |
+--------------+--------------+---------+---------------------+
| 1            | 172.18.3.122 | SHUNNED | 60                  |
| 2            | 172.18.3.122 | SHUNNED | 60                  |
| 2            | 172.18.3.232 | ONLINE  | 60                  |
| 2            | 172.18.4.109 | ONLINE  | 60                  |
+--------------+--------------+---------+---------------------+
4 rows in set (0.00 sec)

(4)确认状态

观察日志脚本执行日志:

bash 复制代码
[mysql@kxvv-yz-mysqlproxy~]$tail -f proxysql_ha.log 
[2026-04-22_13:52:01] 主库正常
[2026-04-22_13:53:01] === 检查开始 ===
[2026-04-22_13:53:01] 当前主库:172.18.3.122:18251
[2026-04-22_13:53:01] WARN:主库宕机,开始自动切换
[2026-04-22_13:53:01] 从库 172.18.3.232:18251 GTID:4ccf3ac8-388e-11f1-84ed-566f5d52007d:1
[2026-04-22_13:53:01] 从库 172.18.4.109:18251 GTID:4ccf3ac8-388e-11f1-84ed-566f5d52007d:1
[2026-04-22_13:53:01] 最优新主:172.18.3.232:18251
[2026-04-22_13:53:01] 提升新主:172.18.3.232:18251
[2026-04-22_13:53:01] 从库 172.18.4.109:18251 指向新主 172.18.3.232:18251
[2026-04-22_13:53:01] === 全部完成!===
[2026-04-22_13:54:01] === 检查开始 ===
[2026-04-22_13:54:01] 当前主库:172.18.3.232:18251
[2026-04-22_13:54:01] 主库正常

查看当前状态:

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+---------+---------------------+
| hostgroup_id | hostname     | status  | max_replication_lag |
+--------------+--------------+---------+---------------------+
| 1            | 172.18.3.122 | SHUNNED | 60                  |
| 1            | 172.18.3.232 | ONLINE  | 60                  |
| 2            | 172.18.3.122 | SHUNNED | 60                  |
| 2            | 172.18.3.232 | ONLINE  | 60                  |
| 2            | 172.18.4.109 | ONLINE  | 60                  |
+--------------+--------------+---------+---------------------+
5 rows in set (0.00 sec)

可以看到:

  • 检测到主库失联。
  • 选出 GTID 最新从库。
  • 自动提升为新主。
  • 另一从库指向新主。
  • ProxySQL HG1 更新为新主。

全部符合预期。新的写组是 172.18.3.232,读组有两个 172.18.3.232 和 172.18.4.109。

在 172.18.3.232:18251 查看新主库状态:

sql 复制代码
mysql> show variables like 'read_only';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only     | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

mysql> show variables like 'super_read_only';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| super_read_only | OFF   |
+-----------------+-------+
1 row in set (0.00 sec)

mysql> show slave status;
Empty set, 1 warning (0.00 sec)

mysql> show slave hosts;
+-----------+------+-------+-----------+--------------------------------------+
| Server_id | Host | Port  | Master_id | Slave_UUID                           |
+-----------+------+-------+-----------+--------------------------------------+
|  10918251 |      | 18251 |  23218251 | f912581a-3a2c-11f1-82fe-8243dbe6270a |
+-----------+------+-------+-----------+--------------------------------------+
1 row in set, 1 warning (0.00 sec)

新主库的只读参数已经全部改为 0,可以读写。自身的从库状态为空,有一个从库。

在 172.18.4.109:18251 查看新从库状态:

sql 复制代码
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.18.3.232
                  Master_User: wxy
                  Master_Port: 18251
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
        Seconds_Behind_Master: 0
...
            Executed_Gtid_Set: 4ccf3ac8-388e-11f1-84ed-566f5d52007d:1
                Auto_Position: 1
...
1 row in set, 1 warning (0.00 sec)

可以看到:该从库已经指向新主库,复制状态正常。综上所述,现在的架构是一主一从,主负责写,主、从负责读,读写分离,读负载均衡。

(3)读写数据

连接 ProxySQL 的业务端口:

bash 复制代码
mysql -u wxy -p -h 127.0.0.1 -P6033

进行一些读写操作:

sql 复制代码
mysql> create table test.t4(a int);
Query OK, 0 rows affected (0.06 sec)

mysql> insert into test.t4 values (1);
Query OK, 1 row affected (0.01 sec)

mysql> select *,@@server_id from test.t4;
+------+-------------+
| a    | @@server_id |
+------+-------------+
|    1 |    23218251 |
+------+-------------+
1 row in set (0.00 sec)

可以看到:可以进行正常的数据读写。此时在 172.18.4.109:18251 查看从库的复制状态,能看到从新主库复制了两个事务(cc59c2e4-3890-11f1-8178-566f5d52007f:1-2):

sql 复制代码
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.18.3.232
                  Master_User: wxy
                  Master_Port: 18251
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
        Seconds_Behind_Master: 0
...
           Retrieved_Gtid_Set: cc59c2e4-3890-11f1-8178-566f5d52007f:1-2
            Executed_Gtid_Set: 4ccf3ac8-388e-11f1-84ed-566f5d52007d:1,
cc59c2e4-3890-11f1-8178-566f5d52007f:1-2
                Auto_Position: 1
...
1 row in set, 1 warning (0.00 sec)

(4)将下线的老主库重新上线

  • 启动数据库
bash 复制代码
# 在 172.18.3.122 执行
mysqld_safe --defaults-file=/home/mysql/my_18251.cnf &
  • 设置为新主库的从库
sql 复制代码
set persist read_only=1;
set persist super_read_only=1;

stop slave;
reset slave all;

change master to 
master_host='172.18.3.232',
master_port=18251,
master_user='wxy',
master_password='123456',
master_auto_position=1;

start slave;
  • 验证复制
sql 复制代码
mysql> show variables like 'read_only';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only     | ON    |
+---------------+-------+
1 row in set (0.00 sec)

mysql> show variables like 'super_read_only';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| super_read_only | ON    |
+-----------------+-------+
1 row in set (0.00 sec)

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.18.3.232
                  Master_User: wxy
                  Master_Port: 18251
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
        Seconds_Behind_Master: 0
...
           Retrieved_Gtid_Set: cc59c2e4-3890-11f1-8178-566f5d52007f:1-2
            Executed_Gtid_Set: 4ccf3ac8-388e-11f1-84ed-566f5d52007d:1,
cc59c2e4-3890-11f1-8178-566f5d52007f:1-2
                Auto_Position: 1
...
1 row in set, 1 warning (0.01 sec)

mysql> select * from test.t4;
+------+
| a    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

重新上线后为只读从库,复制状态正常,离线期间新主库上的事务自动同步。

但是,现在 172.18.3.122 在 ProxySQL 中的状态还是 SHUNNED,同样需要触发流量状态更新。

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+---------+---------------------+
| hostgroup_id | hostname     | status  | max_replication_lag |
+--------------+--------------+---------+---------------------+
| 1            | 172.18.3.232 | ONLINE  | 60                  |
| 2            | 172.18.3.122 | SHUNNED | 60                  |
| 2            | 172.18.3.232 | ONLINE  | 60                  |
| 2            | 172.18.4.109 | ONLINE  | 60                  |
+--------------+--------------+---------+---------------------+
4 rows in set (0.01 sec)

连接 ProxySQL 的 6033 业务端口:

bash 复制代码
mysql -u wxy -p -h 127.0.0.1 -P6033

然后随便执行一条读命令,让它走 HG2 读组:

sql 复制代码
mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|    23218251 |
+-------------+
1 row in set (0.00 sec)

回到 ProxySQL Admin 查看,新上线的从库状态已经变成 ONLINE:

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+--------+---------------------+
| hostgroup_id | hostname     | status | max_replication_lag |
+--------------+--------------+--------+---------------------+
| 1            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.4.109 | ONLINE | 60                  |
+--------------+--------------+--------+---------------------+
4 rows in set (0.00 sec)

至此完全恢复了一写三读、读写分离、读负载均衡的架构。本节的测试是在数据静态不变的情况下进行的,目的只是确认这条自动切换的路径能走通。下节重做这个实验过程,但前提是在模拟业务持续读写情况下进行主从自动失败切换,确认对业务的影响。

3. 业务无感知验证

(1)准备测试表(主库执行)

sql 复制代码
-- 172.18.3.232 执行
CREATE DATABASE IF NOT EXISTS test;
USE test;
CREATE TABLE tt (
    id BIGINT PRIMARY KEY,
    create_time DATETIME DEFAULT NOW()
);

(2)模拟业务持续读写脚本

脚本文件 bench_20conns.sh 文件内容如下:

bash 复制代码
#!/bin/bash
source ~/.bash_profile

CONCURRENCY=20
DELAY=0.1

USER="wxy"
PASS="123456"
HOST="127.0.0.1"
PORT="6033"
DB="test"

worker() {
    local id=$1
    echo "Worker $id 启动 → 长连接已建立"
    
    while true; do
        mysql -u"$USER" -p"$PASS" -h"$HOST" -P"$PORT" -D"$DB" -N <<EOF
SELECT NOW();
INSERT INTO tt(id) VALUES(UNIX_TIMESTAMP());
EOF
        sleep $DELAY
    done
}

echo "启动 $CONCURRENCY 个并发连接..."
for ((i=1; i<=CONCURRENCY; i++)); do
    worker $i &
done
wait
  • 20 个长连接,只建立一次,不会反复新建/断开。
  • 每个连接内部无限循环执行读写(模拟业务读写)。
  • 兼容所有 MySQL/ProxySQL 环境,不会报错。

执行脚本:

bash 复制代码
./bench_20conns.sh

(3)模拟主库宕机(触发自动切换)

  • 查看当前状态
sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+--------+---------------------+
| hostgroup_id | hostname     | status | max_replication_lag |
+--------------+--------------+--------+---------------------+
| 1            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.4.109 | ONLINE | 60                  |
+--------------+--------------+--------+---------------------+
4 rows in set (0.00 sec)
  • 模拟故障
bash 复制代码
# 在 172.18.3.232 执行
kill -9 $(ps -ef | grep 18251 | grep -v grep | awk '{print $2}')
ps -ef | grep 18251
  • 查看当前状态
sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+---------+---------------------+
| hostgroup_id | hostname     | status  | max_replication_lag |
+--------------+--------------+---------+---------------------+
| 1            | 172.18.3.122 | ONLINE  | 60                  |
| 1            | 172.18.3.232 | SHUNNED | 60                  |
| 2            | 172.18.3.122 | ONLINE  | 60                  |
| 2            | 172.18.3.232 | SHUNNED | 60                  |
| 2            | 172.18.4.109 | ONLINE  | 60                  |
+--------------+--------------+---------+---------------------+
5 rows in set (0.00 sec)

(4)查询业务表

在 172.18.4.109:18251 执行:

sql 复制代码
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.18.3.122
                  Master_User: wxy
                  Master_Port: 18251
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
        Seconds_Behind_Master: 0
...
           Retrieved_Gtid_Set: 4ccf3ac8-388e-11f1-84ed-566f5d52007d:2-12
            Executed_Gtid_Set: 4ccf3ac8-388e-11f1-84ed-566f5d52007d:1-12,
cc59c2e4-3890-11f1-8178-566f5d52007f:1-87
                Auto_Position: 1
...
1 row in set, 1 warning (0.00 sec)

mysql> select * from test.tt;
+------------+---------------------+
| id         | create_time         |
+------------+---------------------+
...
| 1776840424 | 2026-04-22 14:47:04 |
| 1776840425 | 2026-04-22 14:47:05 |
| 1776840426 | 2026-04-22 14:47:06 |
| 1776840427 | 2026-04-22 14:47:07 |
| 1776840428 | 2026-04-22 14:47:08 |
| 1776840482 | 2026-04-22 14:48:02 |
| 1776840483 | 2026-04-22 14:48:03 |
| 1776840484 | 2026-04-22 14:48:04 |
| 1776840485 | 2026-04-22 14:48:05 |
...

业务中断时为 14:47:08 ~ 14:48:02,中断时长为 54 秒。这个中断时长与切换脚本的调度有关,本例中使用 crontab 每分钟执行一次,理论上最长影响时间为一分钟。

实验结论:

  • kill 主库的时间 ≈ 14:47:08
  • ProxySQL 探测故障 + 自动切换 + 提升从库 ≈ 54 秒。
  • 切换期间短暂异常,之后自动恢复,无需人工干预。
  • 切换后继续正常读写,主从复制正常,数据无丢失。

(5)恢复宕机实例

  • 启动数据库
bash 复制代码
# 在 172.18.3.232 执行
mysqld_safe --defaults-file=/home/mysql/my_18251.cnf &
  • 设置为新主库的从库
sql 复制代码
set persist read_only=1;
set persist super_read_only=1;

stop slave;
reset slave all;

change master to 
master_host='172.18.3.122',
master_port=18251,
master_user='wxy',
master_password='123456',
master_auto_position=1;

start slave;

重新上线后为只读从库,复制状态正常,离线期间新主库上的事务自动同步。ProxySQL 中的状态复原:

sql 复制代码
ProxySQL Admin> select hostgroup_id,hostname,status,max_replication_lag from runtime_mysql_servers;
+--------------+--------------+--------+---------------------+
| hostgroup_id | hostname     | status | max_replication_lag |
+--------------+--------------+--------+---------------------+
| 1            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.122 | ONLINE | 60                  |
| 2            | 172.18.3.232 | ONLINE | 60                  |
| 2            | 172.18.4.109 | ONLINE | 60                  |
+--------------+--------------+--------+---------------------+
4 rows in set (0.00 sec)

终止数据读写进程:

bash 复制代码
pkill -f 'bench_20conns.sh'
相关推荐
wzy06231 天前
ProxySQL(一)—— 实现 MySQL 读写分离、读负载均衡
负载均衡·读写分离·proxysql
ldj202017 天前
ProxySQL 代理Mysql实现读写分离
读写分离·主从同步·proxysql
散修-小胖子3 个月前
ProxySQL编译报错
mysql·proxysql
號先生1 年前
ProxySQL构建PolarDB-X标准版高可用路由服务三节点集群
proxysql·polardbx·polardbx集群·高可用路由
小时候的阳光2 年前
Docker方式部署ProxySQL和Keepalived组合实现MGR的高可用访问
mysql·docker·keepalived·mgr·proxysql
ZZDICT2 年前
ProxySQL 读写分离配置
proxysql·mysql8.039
todoitbo2 年前
解析ProxySQL的故障转移机制
数据库·故障转移·proxysql
Hello-Brand2 年前
数据库系列:业内主流MySQL数据中间件梳理
mysql·读写分离·分库分表·mycat·proxysql·maxscale·dbproxy·tddl