1. 基准测试的边界认知

在使用 sysbench 之前,必须先明确基准测试能回答什么、不能回答什么,否则很容易得出错误结论。

能回答的问题:

  • 当前硬件和配置下,MySQL 的 TPS/QPS 上限是多少
  • 两套配置方案之间的相对性能差异(如调整 innodb_buffer_pool_size 前后)
  • 某次参数调整或版本升级后,性能是否有可量化的提升或退步
  • 目标并发量下,响应延迟是否在可接受范围内

不能回答的问题:

  • 生产环境的真实性能(基准负载是理想化的,生产 SQL 复杂得多)
  • 长期运行稳定性(基准测试通常持续分钟级)
  • 热点、锁竞争、主从延迟等业务相关的性能问题

sysbench 的结果是性能的参考基线,而非生产能力的直接映射。同一套硬件,sysbench 跑出 5 万 TPS,生产复杂查询下可能只有几千 QPS。


2. 安装

1
2
3
4
5
6
7
8
9
# 添加 Percona yum 源
curl -sSL https://repo.percona.com/yum/percona-release-latest.noarch.rpm -o percona-release.rpm
rpm -ivh percona-release.rpm

# 安装 sysbench
yum install -y sysbench

# 验证版本
sysbench --version

安装后路径:主程序 /usr/bin/sysbench,测试脚本 /usr/share/sysbench/


3. 第一阶段:数据准备(prepare)

3.1 创建测试库与专用账号

1
2
3
4
5
CREATE DATABASE sbtest CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER 'sbtest'@'%' IDENTIFIED BY 'SbTest@2026';
GRANT ALL PRIVILEGES ON sbtest.* TO 'sbtest'@'%';
FLUSH PRIVILEGES;

不要用 root 账号跑压测,测试库与业务库严格隔离。

3.2 执行 prepare 生成测试数据

1
2
3
4
5
6
7
8
9
sysbench oltp_read_write \
  --mysql-host=127.0.0.1 \
  --mysql-port=3306 \
  --mysql-user=sbtest \
  --mysql-password=SbTest@2026 \
  --mysql-db=sbtest \
  --tables=10 \
  --table-size=1000000 \
  prepare

sysbench 会创建 10 张结构一致的测试表(sbtest1 到 sbtest10),每张 100 万行。表结构如下:

1
2
3
4
5
6
7
8
CREATE TABLE sbtest1 (
  id   INT        NOT NULL AUTO_INCREMENT,
  k    INT        NOT NULL DEFAULT 0,
  c    CHAR(120)  NOT NULL DEFAULT '',
  pad  CHAR(60)   NOT NULL DEFAULT '',
  PRIMARY KEY (id),
  KEY k_1 (k)
) ENGINE=InnoDB;

3.3 数据量的选择原则

数据量与 innodb_buffer_pool_size 的比例直接影响测试结果:

数据量 vs buffer pool测试侧重
数据量 < buffer pool大部分读命中内存,测 CPU 和内存带宽上限
数据量约等于 buffer pool 1.5 倍兼顾内存命中和磁盘 I/O,贴近真实场景
数据量远大于 buffer poolI/O 密集型,测存储性能瓶颈

建议:正式压测数据量至少为 buffer pool 的 1.5 倍,使测试包含一定比例的磁盘读取,避免全内存测试失真。


4. 第二阶段:实例预热(prewarm)

预热是容易被忽略但至关重要的环节。MySQL 重启或长时间空闲后,innodb_buffer_pool 是冷的,大量数据页不在内存中,直接压测会导致前几分钟大量磁盘 I/O,TPS 偏低,结果失真。

4.1 为什么必须预热

以本文真实案例为例(2 线程,60 秒压测),未做预热的实时数据如下:

[ 10s ] thds: 2 tps: 23.30  qps: 469.31   lat(95%): 272.27ms  <- 冷启动,P95 高达 272ms
[ 20s ] thds: 2 tps: 77.70  qps: 1554.44  lat(95%):  56.84ms  <- buffer pool 逐渐预热
[ 30s ] thds: 2 tps: 89.00  qps: 1780.01  lat(95%):  46.63ms  <- 趋于稳定
[ 40s ] thds: 2 tps: 92.50  qps: 1849.98  lat(95%):  41.10ms  <- 峰值
[ 50s ] thds: 2 tps: 88.40  qps: 1767.59  lat(95%):  46.63ms
[ 60s ] thds: 2 tps: 90.40  qps: 1807.20  lat(95%):  32.53ms

问题:最终汇总 TPS=76.84,但真实稳定态约 88-92 TPS,被第一个 10 秒的冷启动(TPS=23)严重拉低。汇总 P95=57.87ms,而稳定态 P95 只有 32-46ms,同样被冷启动的 272ms 拉高。

这正是需要预热的原因:让 buffer pool 充分加载热点数据后,再开始正式计时

4.2 sysbench prewarm 子命令(推荐)

prewarm 是 sysbench 的内置子命令(与 prepareruncleanup 并列),会对测试表执行全表扫描,将数据页强制加载进 InnoDB buffer pool:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 \
  --mysql-port=3307 \
  --mysql-user=root \
  --mysql-password=333333 \
  --mysql-db=base07 \
  --table-size=100000 \
  --tables=10 \
  prewarm

prewarm 不是参数,是和 prepare / run / cleanup 并列的子命令,写在命令最后。

4.3 完整四步流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 第一步:生成测试数据
sysbench oltp_read_write ... prepare

# 第二步:预热 buffer pool
sysbench oltp_read_write ... prewarm

# 第三步:执行压测
sysbench oltp_read_write ... run

# 第四步:清理测试数据
sysbench oltp_read_write ... cleanup

4.4 预热后的效果验证

可通过 MySQL 查询 buffer pool 命中率确认预热效果,稳定在 99% 以上再开始压测:

1
2
3
4
5
6
7
SELECT
  FORMAT((1 - a.v / b.v) * 100, 2) AS hit_rate_pct
FROM
  (SELECT VARIABLE_VALUE v FROM performance_schema.global_status
   WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') a,
  (SELECT VARIABLE_VALUE v FROM performance_schema.global_status
   WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') b;

5. 第三阶段:压测执行(run)

5.1 压测命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 \
  --mysql-port=3306 \
  --mysql-user=sbtest \
  --mysql-password=SbTest@2026 \
  --mysql-db=sbtest \
  --tables=10 \
  --table-size=1000000 \
  --threads=16 \
  --time=300 \
  --report-interval=10 \
  run

5.2 关键参数说明

参数说明建议值
--threads并发线程数,模拟并发连接数从 4 开始,逐步翻倍找拐点
--time压测持续时长(秒)不低于 300 秒,排除冷启动干扰
--report-interval实时输出间隔(秒)10,便于观察稳定态区间
--rand-type数据分布类型uniform(均匀)或 pareto(热点模拟)
--table-size每张表的行数根据 buffer pool 大小调整
--tables测试表数量10,减少单表锁竞争

5.3 梯度测试找性能拐点

单次压测结果意义有限,应通过梯度递增线程数来找到系统的性能拐点:

1
2
3
4
5
6
7
for threads in 4 8 16 32 64; do
  sysbench oltp_read_write \
    --threads=$threads \
    --time=120 \
    --report-interval=10 \
    ... run 2>&1 | grep -E "tps|transactions|95th"
done

当 TPS 不再随线程数增长、P95 开始显著上升时,上一个线程数即为最优并发点。


6. 第四阶段:压测结果解析

6.1 真实案例

以下是一次真实压测的完整输出,2 线程、60 秒、oltp_read_write 场景,数据库端口 3307,测试库 base07,10 张表每张 10 万行:

压测命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 \
  --mysql-port=3307 \
  --mysql-user=root \
  --mysql-password=333333 \
  --mysql-db=base07 \
  --table-size=100000 \
  --tables=10 \
  --threads=2 \
  --time=60 \
  --report-interval=10 \
  run

完整输出:

[ 10s ] thds: 2 tps: 23.30  qps: 469.31   (r/w/o: 328.94/93.58/46.79)  lat(95%): 272.27  err/s: 0.00 reconn/s: 0.00
[ 20s ] thds: 2 tps: 77.70  qps: 1554.44  (r/w/o: 1087.83/311.21/155.40) lat(95%):  56.84  err/s: 0.00 reconn/s: 0.00
[ 30s ] thds: 2 tps: 89.00  qps: 1780.01  (r/w/o: 1246.01/356.00/178.00) lat(95%):  46.63  err/s: 0.00 reconn/s: 0.00
[ 40s ] thds: 2 tps: 92.50  qps: 1849.98  (r/w/o: 1294.98/370.00/185.00) lat(95%):  41.10  err/s: 0.00 reconn/s: 0.00
[ 50s ] thds: 2 tps: 88.40  qps: 1767.59  (r/w/o: 1237.60/353.20/176.80) lat(95%):  46.63  err/s: 0.00 reconn/s: 0.00
[ 60s ] thds: 2 tps: 90.40  qps: 1807.20  (r/w/o: 1264.80/361.60/180.80) lat(95%):  32.53  err/s: 0.00 reconn/s: 0.00

SQL statistics:
    queries performed:
        read:                            64610
        write:                           18460
        other:                           9230
        total:                           92300
    transactions:                        4615   (76.84 per sec.)
    queries:                             92300  (1536.81 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          60.0580s
    total number of events:              4615

Latency (ms):
         min:                                    5.61
         avg:                                   26.01
         max:                                  838.69
         95th percentile:                       57.87
         sum:                               120018.85

Threads fairness:
    events (avg/stddev):           2307.5000/6.50
    execution time (avg/stddev):   60.0094/0.01

6.2 实时输出逐行解读

时间点TPSQPSP95(ms)分析
10s23.30469272.27冷启动:buffer pool 未预热,大量磁盘 I/O,延迟极高
20s77.70155456.84buffer pool 逐渐加载热点页,TPS 快速回升
30s89.00178046.63趋于稳定,已进入热态
40s92.50184941.10峰值,buffer pool 命中率最高
50s88.40176746.63正常波动,稳定态
60s90.40180732.53持续稳定

核心结论:第 10 秒是冷启动噪声,真实稳定态 TPS 约为 88-92,稳定态 P95 约为 32-46ms


6.3 三大核心指标详解

TPS(Transactions Per Second,每秒事务数)

transactions: 4615 (76.84 per sec.)
  • 4615:60 秒内完成的总事务数
  • 76.84:全程平均 TPS,含冷启动的 10s,被严重拉低
  • 真实稳定态 TPS 约为 88-92(以 30-60s 区间为准)

TPS 的本质:一次事务从 BEGINCOMMIT 算一个完整事务。oltp_read_write 每个事务默认包含约 20 条 SQL(10 次 point select + range/order/distinct select + update + delete + insert)。

QPS(Queries Per Second,每秒查询数)

queries: 92300 (1536.81 per sec.)
  • 全程共执行 92300 条 SQL,平均每秒 1536.81 条
  • 换算验证:TPS x 每事务SQL数 = 76.84 x 20 = 1536.8,完全吻合
  • read:write:other = 64610:18460:9230,约为 70%:20%:10%,符合 oltp_read_write 的读写比设计

LAT(Latency,响应延迟)

Latency (ms):
     min:                    5.61    <- 最优单次响应(内存命中+无竞争)
     avg:                   26.01    <- 全程平均,含冷启动拉高
     max:                  838.69   <- 冷启动阶段的极端慢请求
     95th percentile:       57.87   <- 全程 P95,含冷启动影响

含冷启动 vs 稳定态对比

指标全程汇总(含冷启动)稳定态(30-60s)
TPS76.8488-92
P9557.87ms32-46ms
max838.69ms约 50-100ms

max=838ms 分析:发生在第 10s 冷启动阶段,是 buffer pool 未加载时磁盘 I/O 阻塞导致的极端值,不代表系统稳定运行时的真实水平。若做了 prewarm 预热,max 值会大幅下降。


6.4 汇总报告逐段解读

SQL statistics:
    queries performed:
        read:    64610   # SELECT 类语句:point select、range select 等
        write:   18460   # INSERT / UPDATE / DELETE
        other:    9230   # BEGIN / COMMIT 等控制语句
        total:   92300

read:write 约为 3.5:1,是 oltp_read_write 的典型比例(读密集型混合负载)。

    transactions:   4615  (76.84 per sec.)   # TPS
    queries:       92300  (1536.81 per sec.) # QPS
    ignored errors:    0  (0.00 per sec.)    # 无报错,连接正常
    reconnects:        0  (0.00 per sec.)    # 无重连,网络稳定
General statistics:
    total time:                 60.0580s   # 实际运行时长(略超 60s 属正常)
    total number of events:     4615       # 总事务数
Latency (ms):
     min:        5.61    # 最快的单次事务
     avg:       26.01    # 平均响应时间(受冷启动拉高)
     max:      838.69    # 最慢的单次事务(冷启动阶段)
     95th:      57.87    # P95:95% 的事务在 57.87ms 内完成
     sum:   120018.85    # 所有事务响应时间之和(用于计算 avg)
Threads fairness:
    events (avg/stddev):          2307.5 / 6.50   # 每线程处理事务数均值 / 标准差
    execution time (avg/stddev):  60.0094 / 0.01  # 每线程运行时间均值 / 标准差

Threads fairness 解读

  • 2 个线程分别处理了约 2307.5 次事务,标准差仅 6.5(偏差率 0.28%),说明负载极为均衡
  • 两个线程运行时间几乎完全一致(标准差 0.01s),没有线程饥饿或阻塞问题
  • stddev 越小越好,若 stddev 很大说明存在热点锁竞争或资源分配不均

6.5 本次压测结论

项目数值评估
稳定态 TPS88-922 线程下的吞吐上限
稳定态 P9532-46ms延迟可接受
冷启动 P95272ms预热前延迟极高,需做 prewarm
线程负载均衡stddev=6.5优秀,无竞争热点
错误/重连0连接稳定

优化建议:本次未做 prewarm 预热,导致汇总 TPS(76.84)与稳定态 TPS(88-92)相差约 15%,汇总 P95(57.87ms)比稳定态 P95(32-46ms)高约 25%。正式压测前应先执行 prewarm 子命令,再运行 run,确保结果真实反映系统热态性能。


7. 清理

1
2
3
4
5
6
7
8
9
sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 \
  --mysql-port=3307 \
  --mysql-user=root \
  --mysql-password=333333 \
  --mysql-db=base07 \
  --tables=10 \
  cleanup

8. 常见误区

  • 不做预热直接压测:冷启动阶段 TPS 和延迟数据严重失真,应先执行 prewarm 子命令
  • 压测时间太短:60 秒含冷启动后实际稳定窗口不足,建议不低于 300 秒
  • 只看汇总 TPS 不看实时数据:汇总 TPS 被冷启动拉低,应结合实时输出识别稳定态区间
  • 只看 TPS 不看 P95:高 TPS 配合高 P95 在生产中不可接受,二者须同时满足 SLA
  • 被测 MySQL 与 sysbench 同机运行:二者争抢资源,结果失真,应分机测试

9. 小结

完整的 sysbench 压测分四个阶段:

  1. 数据准备(prepare):生成合理大小的测试数据,建议数据量为 buffer pool 的 1.5 倍以上
  2. 实例预热(prewarm):使用 sysbench 内置 prewarm 子命令全表扫描,将热点页加载进 buffer pool,避免冷启动噪声污染结果
  3. 压测执行(run):梯度递增线程数找拐点,--time 不低于 300 秒,以稳定阶段实时数据为准
  4. 结果解析:TPS 看吞吐,QPS 看总 SQL 量,P95 是最核心的 SLA 指标;结合实时输出区分冷启动区间与稳定态,配合系统监控定位瓶颈