ProxySQL(四)—— 基准测试

目录

一、测试目的

二、压测工具与方案选型说明

[1. 主流工具 / 方案舍弃原因](#1. 主流工具 / 方案舍弃原因)

[2. 最终方案:自定义压测脚本 bench.sh](#2. 最终方案:自定义压测脚本 bench.sh)

三、测试环境

四、测试数据初始化(主库执行)

[1. 建库建表](#1. 建库建表)

[2. 生成测试数据](#2. 生成测试数据)

[3. 导出为压测专用备份文件供脚本使用](#3. 导出为压测专用备份文件供脚本使用)

[五、ProxySQL 配置](#五、ProxySQL 配置)

[1. 主机组配置](#1. 主机组配置)

[2. 路由规则](#2. 路由规则)

[六、测试脚本 bench.sh 思路、设计与实现](#六、测试脚本 bench.sh 思路、设计与实现)

[1. 核心前提](#1. 核心前提)

[2. 设计目标](#2. 设计目标)

[3. 整体架构(三进程模型)](#3. 整体架构(三进程模型))

[4. 执行流程](#4. 执行流程)

[5. 关键设计](#5. 关键设计)

[6. 完整测试脚本 bench.sh](#6. 完整测试脚本 bench.sh)

七、测试执行命令

八、实测结果说明

[1. 实测数据汇总表](#1. 实测数据汇总表)

[2. 测试结果说明](#2. 测试结果说明)

(1)性能提升

[(2)主库 CPU 负载](#(2)主库 CPU 负载)

[(3)主库磁盘 IO](#(3)主库磁盘 IO)

(4)稳定性与扩展性

九、最终总结


在公开权威测试中,仅 Percona 发布过 ProxySQL 2.x 系列在 TPCC 混合负载下的性能损耗数据:相比直接连接 MySQL,ProxySQL 会带来约 2%~6% 的吞吐量下降与 0.3~0.5ms 的 95% 延迟增加。(https://www.percona.com/blog/proxysql-2-6-performance-improvements-and-benchmarks/,原文虽下架,但数据已被业界广泛引用、存档,是目前唯一可信的 ProxySQL 纯开销数据。)

|--------|--------------|------------------|----------|
| 并发 | 直连 MySQL | ProxySQL 2.6 | 延迟增加 |
| 8 | 7.3 ms | 7.8 ms | +0.5 ms |
| 16 | 8.1 ms | 8.6 ms | +0.5 ms |
| 32 | 9.2 ms | 9.6 ms | +0.4 ms |
| 64 | 11.5 ms | 11.9 ms | +0.4 ms |
| 128 | 15.8 ms | 16.1 ms | +0.3 ms |

|--------|--------------|------------------|----------|
| 并发 | 直连 MySQL | ProxySQL 2.6 | 性能损耗 |
| 8 | 1,017 | 951 | -6.5% |
| 16 | 1,821 | 1,711 | -6.0% |
| 32 | 3,158 | 3,011 | -4.7% |
| 64 | 5,011 | 4,872 | -2.8% |
| 128 | 6,418 | 6,299 | -1.9% |

目前不存在权威机构发布的、基于 ProxySQL 实现主从读写分离架构并与直连主库进行对比的官方基准测试数据。读写分离带来的整体性能提升依赖业务读写比例、主从资源瓶颈等环境因素,需通过实测获得量化结果。

一、测试目的

模拟线上 80% 读、20% 写短事务业务场景,对比直连 MySQL 主库与 ProxySQL 2.7.3 读写分离架构的吞吐量(QPS)、响应时延、主库 CPU 平均使用率、主库磁盘 IO % util。量化读写分离的降压与性能提升效果,为生产环境落地提供真实、可复现、可追溯的测试依据。

二、压测工具与方案选型说明

1. 主流工具 / 方案舍弃原因

  • Sysbench:默认采用长事务 / 多语句事务模型,与 tpcc-mysql 一致。由于 ProxySQL 机制限制,事务内所有语句不做读写分离,全部强制走主库,导致读写分离完全失效,测试结果无效。
  • tpcc-mysql:典型长事务、复杂交易模型,事务内混合读写,ProxySQL 同样不做读写分离,全部路由主库,与本次短单语句、读多写少场景不匹配。
  • 存储过程:ProxySQL 无法解析存储过程内部 SQL,所有语句强制走主库,读写分离失效。
  • Shell 短连接逐行执行:每条 SQL 新建 / 断开连接,无法复用连接,性能失真,不贴合生产连接池模型。
  • mysqlslap:无法自定义真随机读写比例、无法每次重建基准数据、长连接不稳定,不满足本次测试要求。

2. 最终方案:自定义压测脚本 bench.sh

  • 短单语句自动提交:确保 ProxySQL 读写分离 100% 生效。
  • 固定总请求量:所有用例总 SQL 数量完全一致,结果公平可对比。
  • 长连接复用:每个并发仅建立一次连接,批量执行 SQL,符合生产环境的连接池特性。
  • 本机压测:脚本运行在 MySQL 主库所在主机,排除网络波动干扰。
  • 双进程独立监控:压测 + CPU/IO 监控完全隔离,数据真实。
  • 环境初始化:每次自动重建库、重新导入相同的测试数据、分析表,使得每轮测试起跑线完全一致。

三、测试环境

  • MySQL 版本:8.0.22
  • 架构:一主(10.252.1.162:18251)两从(10.252.1.163:18251、10.252.1.168:18251),半同步复制,GTID + AUTO_POSITION
  • ProxySQL 版本:2.7.3
  • ProxySQL 配置:一写三读(hostgroup 1 写 | hostgroup 2 读),读写分离 + 读负载均衡
  • 压测执行位置:MySQL 主库本机
  • 测试表:user_order,100 万行数据,主键 + 索引齐全。

四、测试数据初始化(主库执行)

1. 建库建表

sql 复制代码
DROP DATABASE IF EXISTS test_bench;
CREATE DATABASE test_bench;
USE test_bench;

CREATE TABLE user_order (
  id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id int NOT NULL,
  order_no varchar(64) NOT NULL,
  status tinyint NOT NULL,
  create_time datetime NOT NULL,

  UNIQUE KEY uk_order_no (order_no), 
  KEY idx_user_id (user_id)
) ENGINE=InnoDB;

2. 生成测试数据

sql 复制代码
USE test_bench;
DROP PROCEDURE IF EXISTS init_data;

DELIMITER //
CREATE PROCEDURE init_data()
BEGIN
  DECLARE i INT DEFAULT 1;
  WHILE i <= 1000000 DO
    INSERT INTO user_order(id, user_id, order_no, status, create_time)
    VALUES(
      i,
      FLOOR(RAND() * 1000000),
      CONCAT('ORDER_', i),
      FLOOR(RAND() * 5),
      DATE_SUB(NOW(), INTERVAL FLOOR(RAND()*365) DAY)
    );
    SET i = i + 1;
  END WHILE;
END //
DELIMITER ;

CALL init_data();

3. 导出为压测专用备份文件供脚本使用

bash 复制代码
mysqldump -uwxy -p123456 -h10.252.1.162 -P18251 \
--set-gtid-purged=OFF \
--single-transaction \
test_bench user_order > user_order_100w.sql

生成文件:user_order_100w.sql,与 bench.sh 同目录。

五、ProxySQL 配置

1. 主机组配置

bash 复制代码
+--------------+--------------+-------+--------+--------+-----------------+---------------------+
| hostgroup_id | hostname     | port  | status | weight | max_connections | max_replication_lag |
+--------------+--------------+-------+--------+--------+-----------------+---------------------+
| 1            | 10.252.1.162 | 18251 | ONLINE | 1      | 1000            | 0                   |
| 2            | 10.252.1.162 | 18251 | ONLINE | 1      | 1000            | 0                   |
| 2            | 10.252.1.163 | 18251 | ONLINE | 1      | 1000            | 0                   |
| 2            | 10.252.1.168 | 18251 | ONLINE | 1      | 1000            | 0                   |
+--------------+--------------+-------+--------+--------+-----------------+---------------------+

2. 路由规则

bash 复制代码
+---------+--------+-----------------------------+-----------------------+
| rule_id | active | match_digest                | destination_hostgroup |
+---------+--------+-----------------------------+-----------------------+
| 1       | 1      | ^INSERT                     | 1                     |
| 2       | 1      | ^UPDATE                     | 1                     |
| 3       | 1      | ^DELETE                     | 1                     |
| 4       | 1      | ^REPLACE                    | 1                     |
| 5       | 1      | ^MERGE                      | 1                     |
| 10      | 1      | ^CREATE                     | 1                     |
| 11      | 1      | ^ALTER                      | 1                     |
| 12      | 1      | ^DROP                       | 1                     |
| 13      | 1      | ^TRUNCATE                   | 1                     |
| 14      | 1      | ^RENAME                     | 1                     |
| 15      | 1      | ^GRANT                      | 1                     |
| 16      | 1      | ^REVOKE                     | 1                     |
| 20      | 1      | ^SELECT.*FOR UPDATE         | 1                     |
| 21      | 1      | ^SELECT.*LOCK IN SHARE MODE | 1                     |
| 100     | 1      | ^SELECT                     | 2                     |
+---------+--------+-----------------------------+-----------------------+

六、测试脚本 bench.sh 思路、设计与实现

1. 核心前提

本测试脚本直接在 MySQL 主库所在主机上运行。

  • 压测客户端 = 主库本机
  • CPU/IO 监控数据 = 主库真实资源使用率
  • 无网络损耗、无跨机干扰,数据最真实、最严谨

2. 设计目标

  • 模拟生产真实短事务:单语句自动提交,保证 ProxySQL 读写分离生效。
  • 固定总请求量:总 SQL 数 = 并发数 × 2500,确保对比公平。
  • 长连接复用:每个并发只建立一次连接,执行一批 SQL,避免连接抖动。
  • 双进程独立监控:CPU、IO 监控与压测逻辑完全隔离,不干扰压测。
  • 环境绝对干净:每次压测自动重建库、导入 100 万数据、ANALYZE。
  • 自动统计输出:自动计算平均 CPU、平均 IO、峰值、耗时。

3. 整体架构(三进程模型)

  • 主进程:参数解析 → 环境初始化 → 启动监控 → 并发压测 → 结果计算。
  • CPU 监控进程:0.5s 高频采样,记录 us+sy 总使用率。
  • IO 监控进程:1s 采样,采集磁盘 dm-0 的 %util 指标。

4. 执行流程

  1. 接收参数:master(直连)/ proxysql、并发数。
  2. 初始化环境:DROP/CREATE DATABASE → 导入 100 万数据 → ANALYZE。
  3. 启动 CPU、IO 独立后台监控进程。
  4. 开启 N 个并发任务:每个任务先生成 2500 条随机 SQL;通过单个长连接一次性批量执行。
  5. 等待所有并发完成。
  6. 停止监控 → 计算平均值 → 输出结果 → 清理临时文件。

5. 关键设计

  • 本机压测:消除网络延迟,只对比数据库与代理本身性能。
  • 批量 SQL + 长连接:完全贴合生产 JDBC / 连接池使用模型。
  • 独立监控:监控不抢占压测资源,数据准确。
  • 无侵入、无残留:执行完自动清理,可重复、可批量运行。

6. 完整测试脚本 bench.sh

bash 复制代码
#!/bin/bash
# 最终稳定版 - 双进程监控,确保IO数据准确
# 用法:./bench.sh master 10
# 用法:./bench.sh proxysql 10

if [ $# -ne 2 ]; then
    echo "用法:$0 <master|proxysql> <并发数>"
    exit 1
fi

MODE="$1"
CONCUR="$2"

USER="wxy"
PASS="123456"
DB="test_bench"
DUMP_FILE="./user_order_100w.sql"
LOOP=2500

if [ "$MODE" = "master" ]; then
    HOST="10.252.1.162"
    PORT="18251"
else
    HOST="127.0.0.1"
    PORT="6033"
fi

echo "====================================="
echo "初始化:重建库并导入100w数据"
echo "====================================="
mysql -h$HOST -P$PORT -u$USER -p$PASS -e "DROP DATABASE IF EXISTS $DB; CREATE DATABASE $DB;" >/dev/null 2>&1
mysql -h$HOST -P$PORT -u$USER -p$PASS $DB < $DUMP_FILE >/dev/null 2>&1
mysql -h$HOST -P$PORT -u$USER -p$PASS -D $DB -e "ANALYZE TABLE user_order;" >/dev/null 2>&1

echo "压测模式: $MODE   并发: $CONCUR"
echo "比例: SELECT80% INSERT10% UPDATE9% DELETE1%"
echo "====================================="

# 创建临时文件
CPU_FILE=$(mktemp)
IO_FILE=$(mktemp)

# ============================================
# CPU监控进程(高频采样,0.5秒间隔)
# ============================================
cpu_monitor() {
    while true; do
        cpu_line=$(top -bn1 | grep "Cpu(s)" | head -1)
        us=$(echo "$cpu_line" | awk '{print $2}' | cut -d'%' -f1)
        sy=$(echo "$cpu_line" | awk '{print $4}' | cut -d'%' -f1)
        if [[ "$us" =~ ^[0-9.]+$ ]] && [[ "$sy" =~ ^[0-9.]+$ ]]; then
            cpu_usage=$(echo "$us + $sy" | bc)
            echo "$cpu_usage" >> $CPU_FILE
        fi
        sleep 0.5
    done
}

# ============================================
# IO监控进程(低频准确采样,2秒间隔)
# ============================================
io_monitor() {
    while true; do
        # 使用2秒采样周期,取最后一次结果(最准确)
        io_util=$(iostat -x 1 2 | grep "dm-0" | tail -1 | awk '{print $NF}')
        if [[ "$io_util" =~ ^[0-9.]+$ ]]; then
            echo "$io_util" >> $IO_FILE
        fi
        sleep 1
    done
}

# 启动两个独立的监控进程
cpu_monitor &
CPU_PID=$!
io_monitor &
IO_PID=$!

echo "监控已启动 (CPU-PID: $CPU_PID, IO-PID: $IO_PID)"
sleep 2

# ============================================
# 开始压测
# ============================================
START=$(date +%s.%N)

# 记录所有子进程PID
declare -a pids=()

# 启动并发任务
for ((i=0; i<CONCUR; i++)); do
    (
        sql_batch=""
        for ((k=0; k<LOOP; k++)); do
            rnd=$((RANDOM % 100))
            if [ $rnd -lt 80 ]; then
                sql_batch+="SELECT id,user_id,order_no,status,create_time FROM user_order WHERE id > FLOOR(RAND()*1000000) LIMIT 1;"$'\n'
            elif [ $rnd -lt 90 ]; then
                nid=$((1000000 + RANDOM%1000000))
                sql_batch+="INSERT IGNORE INTO user_order(id,user_id,order_no,status,create_time) VALUES($nid,$nid,CONCAT('ORD_',$nid),1,NOW());"$'\n'
            elif [ $rnd -lt 99 ]; then
                uid=$((1 + RANDOM%1000000))
                sql_batch+="UPDATE user_order SET status=status+1 WHERE id=$uid;"$'\n'
            else
                did=$((1 + RANDOM%1000000))
                sql_batch+="DELETE FROM user_order WHERE id=$did;"$'\n'
            fi
        done
        
        echo "$sql_batch" | mysql -h$HOST -P$PORT -u$USER -p$PASS -D $DB -s -N 2>/dev/null 1>/dev/null
    ) &
    pids+=($!)
done

# 等待所有压测进程完成
echo "等待 $CONCUR 个并发任务完成..."
for pid in "${pids[@]}"; do
    wait $pid 2>/dev/null
done

END=$(date +%s.%N)
cost=$(echo "scale=3; $END - $START" | bc)

# ============================================
# 停止监控进程
# ============================================
kill $CPU_PID $IO_PID 2>/dev/null
sleep 1

# ============================================
# 计算平均值
# ============================================
avg_cpu=$(awk '{sum+=$1; count++} END {if(count>0) printf "%.2f", sum/count}' $CPU_FILE 2>/dev/null)
avg_io=$(awk '{sum+=$1; count++} END {if(count>0) printf "%.2f", sum/count}' $IO_FILE 2>/dev/null)
cpu_samples=$(wc -l < $CPU_FILE 2>/dev/null | tr -d ' ')
io_samples=$(wc -l < $IO_FILE 2>/dev/null | tr -d ' ')

# 可选:输出最大值和最小值用于参考
max_cpu=$(awk 'BEGIN{max=0}{if($1>max) max=$1}END{printf "%.2f", max}' $CPU_FILE 2>/dev/null)
max_io=$(awk 'BEGIN{max=0}{if($1>max) max=$1}END{printf "%.2f", max}' $IO_FILE 2>/dev/null)

# 清理临时文件
rm -f $CPU_FILE $IO_FILE

# ============================================
# 输出结果
# ============================================
echo -e "\n✅ 测试完成"
echo "====================================="
echo "模式:$MODE"
echo "并发数:$CONCUR"
echo "总耗时:$cost 秒"
echo "====================================="
echo "CPU统计:"
echo "  采样次数:$cpu_samples 次"
echo "  平均值:${avg_cpu}%"
echo "  峰值:${max_cpu}%"
echo "====================================="
echo "IO统计 (/data 磁盘):"
echo "  采样次数:$io_samples 次"
echo "  平均值:${avg_io}%"
echo "  峰值:${max_io}%"
echo "====================================="

七、测试执行命令

bash 复制代码
./bench.sh master 10
./bench.sh proxysql 10
./bench.sh master 20
./bench.sh proxysql 20
./bench.sh master 40
./bench.sh proxysql 40
./bench.sh master 80
./bench.sh proxysql 80
./bench.sh master 160
./bench.sh proxysql 160

八、实测结果说明

1. 实测数据汇总表

并发 连接方式 总耗时 (s) 总请求数 QPS (约) 主库 CPU 平均使用率 主库 IO % util 平均值 性能提升
10 直连 master 2.90 25000 8620 56.84% 4.50% ---
10 ProxySQL 1.55 25000 16120 30.00% 4.85% 1.87 倍
20 直连 master 5.56 50000 8990 72.90% 3.33% ---
20 ProxySQL 2.80 50000 17850 35.66% 3.85% 1.99 倍
40 直连 master 11.35 100000 8810 76.10% 3.07% ---
40 ProxySQL 5.74 100000 17420 39.54% 1.85% 1.98 倍
80 直连 master 22.75 200000 8790 83.10% 2.22% ---
80 ProxySQL 11.33 200000 17650 45.51% 1.54% 2.01 倍
160 直连 master 44.29 400000 9030 87.06% 1.72% ---
160 ProxySQL 22.06 400000 18130 46.62% 2.29% 2.01 倍

2. 测试结果说明

(1)性能提升

  • 直连主库:QPS 上限约 9000,CPU 成为瓶颈
  • ProxySQL 架构:QPS 稳定 1.7~1.8 万
  • 整体性能提升 ≈ 100%(2 倍)

(2)主库 CPU 负载

  • 直连主库:CPU 随并发飙升,最高达 87.06%
  • ProxySQL:CPU 始终平稳,最高仅 46.62%
  • 读写分离后,主库 CPU 平均降低 45%~50%

(3)主库磁盘 IO

  • 所有场景 IO % util 均处于极低水平。
  • ProxySQL 未增加主库磁盘压力。
  • 读 IO 已完全转移至读库集群。

(4)稳定性与扩展性

  • 并发越高,ProxySQL 优势越明显。
  • 主库资源消耗大幅下降,系统稳定性显著提升。

九、最终总结

  • 测试脚本运行于主库本机,无网络干扰,所有 CPU/IO 数据为主库真实指标。
  • 自定义短事务脚本是唯一能让 ProxySQL 读写分离完整生效的压测方案。
  • 建库 → 建表 → 数据初始化 → 自动导入,全套可复现。
  • ProxySQL 可使系统性能提升 100%,主库 CPU 降低近 50%。
  • 整套方案可复现、可核对。
相关推荐
wzy06231 天前
ProxySQL(五)—— 代理多组独立的 MySQL 主从实例
proxysql
wzy06235 天前
ProxySQL(三)—— 数据分片
分库分表·proxysql·sharding
wzy06236 天前
ProxySQL(二)—— 实现 MySQL 主从自动失败切换
proxysql·主从自动切换
wzy06237 天前
ProxySQL(一)—— 实现 MySQL 读写分离、读负载均衡
负载均衡·读写分离·proxysql
ldj202023 天前
ProxySQL 代理Mysql实现读写分离
读写分离·主从同步·proxysql
散修-小胖子3 个月前
ProxySQL编译报错
mysql·proxysql
_OP_CHEN3 个月前
【测试理论和实践】(十一)吃透性能测试核心概念!从入门到精通,一文扫清所有盲区
测试开发·压力测试·性能测试·负载测试·并发测试·基准测试·测试开发工程师
简简单单OnlineZuozuo4 个月前
提示架构:设计可靠、确定性的AI系统
人工智能·unity·架构·游戏引擎·基准测试·the stanford ai·儿童
HyperAI超神经4 个月前
GPT-5全面领先,OpenAI发布FrontierScience,「推理+科研」双轨检验大模型能力
人工智能·gpt·ai·openai·benchmark·基准测试·gpt5.2