数据库压测实战:用 sysbench 与 pgbench 量化性能

很多团队在上线前才临时”用手点一下”,凭感觉判断数据库”应该扛得住”。真正的容量规划需要可复现的数字:数据库压测就是用 sysbench 和 pgbench 这样的基准工具,把 QPS、TPS、p99 延迟变成可以横向对比的基线。本文带你从零跑通 MySQL/MariaDB 与 PostgreSQL 的压测全流程,并把它接进 CI 做持续容量守护。

一、压测前先想清楚三件事

在敲命令之前,先回答三个问题,否则压出来的数字既不好看也没用:

  • 目标是什么:验证单实例上限?对比索引优化前后差异?还是为扩容找依据?目标不同,加压方式完全不同。
  • 基线在哪儿:先在不加压时记录空闲指标(CPU、磁盘 IO、连接数),有了基线才能判断”掉点是被压出来的还是本来就慢”。
  • 变量只动一个:每次只改一个因素(比如是否开慢查询、是否换 SSD),其余配置锁死,结论才站得住。

二、sysbench 压测 MySQL / MariaDB

sysbench 是跨数据库、跨场景的基准瑞士军刀,内置 oltp_read_write、oltp_read_only、oltp_write_only 等 Lua 脚本,最适合快速拿到 MySQL 系的吞吐与延迟曲线。

2.1 准备测试库与数据

先建一个独立的测试库,再灌入 10 张表、每张 10 万行作为初始数据集。生产库绝不直接压。

# 准备阶段:建表 + 灌数据(--threads 控制并发灌入)
sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 --mysql-port=3306 \
  --mysql-user=bench --mysql-password=benchpwd \
  --mysql-db=sbtest \
  --tables=10 --table-size=100000 \
  --threads=8 \
  prepare

2.2 跑 OLTP 读写混合

正式加压用 run,时长建议 300 秒以上,让缓存预热、抖动收敛后再看稳态值。

sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 --mysql-port=3306 \
  --mysql-user=bench --mysql-password=benchpwd \
  --mysql-db=sbtest \
  --tables=10 --table-size=100000 \
  --threads=32 --time=300 --report-interval=10 \
  run

输出里最该盯的是 transactions(总事务数)、queries(总查询数)和尾部的 latency 分布。把 threads 从 8 逐步加到 64、128,你就能画出一条”并发-吞吐”曲线,找到拐点。

2.3 只读 / 只写拆开看瓶颈

读写混在一起时,瓶颈可能是磁盘也可能是锁。拆成两种场景分别跑,能定位是 CPU 密集还是 IO 密集:

# 只读(验证缓存命中与读扩展能力)
sysbench oltp_read_only  --threads=64 --time=300 run
# 只写(验证 redo/undo、磁盘刷盘能力)
sysbench oltp_write_only --threads=64 --time=300 run

2.4 读懂 sysbench 的关键输出

一轮 run 结束,sysbench 会打印汇总。下面是一段典型的只读测试结果,重点看三处:

SQL statistics:
    queries performed:
        read:                            4820000
        write:                           0
        other:                           0
        total:                           4820000
    transactions:                        301250 (1004.17 per sec.)
    queries:                             4820000 (16066.74 per sec.)
Latency (ms):
         min:                                    3.21
         avg:                                   31.86
         max:                                  412.50
         95th percentile:                       78.42
         sum:                              9598000.00

transactions 行括号内的数字就是 TPS;Latency 段的 95th percentile 是 p95 延迟,max 则是极端尾延迟。当并发继续加大,若 TPS 不再上升而 avg 与 p95 同步飙升,说明已到系统拐点,再加压只会更慢。把这段数字和前面阶梯加压的结果连起来,就是一份可交付的容量报告,也能直接作为 CI 门禁的对比基线。

关键参数作用建议
–threads并发客户端数从 8 阶梯加到超过 CPU 核数
–time单轮时长(秒)≥ 300,避免短时抖动
–table-size单表行数越大越接近真实数据集
–report-interval中间结果间隔10s,观察是否收敛

三、pgbench 压测 PostgreSQL

PostgreSQL 自带 pgbench,内置经典的 TPC-B 类事务脚本(含 UPDATE、INSERT、SELECT),也能用 -f 跑你自己的业务 SQL。

3.1 初始化与内置压测

# 初始化测试库,-s 控制数据规模(scale factor)
pgbench -h 127.0.0.1 -U bench -d sbtest -i -s 100

# 跑 300 秒、64 并发的内置 TPC-B 事务
pgbench -h 127.0.0.1 -U bench -d sbtest \
  -c 64 -j 8 -T 300 -P 10

3.2 用自定义脚本贴近真实业务

内置脚本毕竟是通用负载。把真实查询写成 SQL 文件,用 -f 指定,才能让压测反映你业务的真实读写比:

\set aid random(1, 100000)
\set bid random(1, 1000)
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
UPDATE pgbench_accounts SET abalance = abalance + 1 WHERE aid = :aid;

把上面内容存为 workload.sql,执行 pgbench -f workload.sql -c 64 -T 300 即可。注意 -c 是客户端连接数、-j 是工作线程数,二者配合才能打满连接池。

维度sysbench(MySQL 系)pgbench(PG 系)
数据准备prepare 子命令-i 初始化
并发参数–threads-c 连接 / -j 线程
自定义负载自带 Lua 脚本有限-f 写任意 SQL
输出重点事务/查询吞吐 + 延迟分布tps + 延迟

四、关键指标到底怎么读

压测报告一大堆数字,真正决定容量结论的其实就四个:

指标含义怎么用
QPS / TPS每秒查询 / 事务数衡量绝对吞吐,看拐点
p95 / p99 延迟尾延迟比平均值更能暴露卡顿
延迟抖动不同时段最大最小差判断稳定性
错误率超时 / 连接失败占比超过 1% 即视为过载

平均值会骗人:平均 5ms 可能掩盖了 1% 请求 2 秒的灾难。容量规划一律以 p99 为准,再留 30% 余量。

五、压测最常见的 5 个坑

  • 压测机比数据库还弱:压测客户端 CPU 打满,数字自然上不去,先确认压测机不是瓶颈。
  • 没预热:缓存冷的瞬间吞吐极低,前 60 秒数据要丢弃。
  • 关了持久化:为了数字好看关掉 fsync / redo,结果和线上完全两样,结论无效。
  • 网络和线上不一致:本地回环压测延迟失真,至少用同可用区机器。
  • 只看平均值:务必看 p99 与错误率,否则上线即翻车。

六、把压测写进 CI,做持续容量守护

一次性压测只能说明当下。把”固定并发下的 TPS 不低于基线 90%”做成回归门禁,每次改 schema 或加索引都能自动发现性能回退:

# .github/workflows/bench.yml 片段
- name: Run DB benchmark gate
  run: |
    TPS=$(sysbench oltp_read_write --threads=32 --time=120 run \
          | grep "transactions:" | awk '{print $3}')
    python3 -c "import sys; sys.exit(0 if float('$TPS') >= BASELINE*0.9 else 1)"

配合 Docker 起一个干净数据库实例,每次跑在隔离环境里,结果才可复现。这也是 用 Docker 快速搭隔离测试环境 的价值所在。

七、一个真实的容量规划案例

某订单服务单机 MySQL 在 32 并发下 TPS 约 4200,但 p99 延迟随并发上升陡增。排查发现热点表缺少合适索引,MySQL 索引底层原理 里提到的回表放大了随机 IO;补索引后 TPS 提升到 6800。再用 PostgreSQL 慢查询分析 同思路复核只读副本,最终把连接池按 HikariCP 调优 的”连接数 ≈ 核心数×2 + 磁盘数”经验值设定,整体稳定性显著改善。整个过程遵循 数据库设计规范 的命名与建模约定,避免了一开始就埋坑。压测不是终点,而是把”感觉”变成”数字”的闭环。

八、落地清单

  • 明确目标与基线,变量只动一个;
  • 用 sysbench / pgbench 阶梯加压,画并发-吞吐曲线;
  • 只读、只写分开跑,定位 CPU 还是 IO 瓶颈;
  • 以 p99 延迟 + 错误率为容量判据,留 30% 余量;
  • 把压测做成 CI 门禁,持续守护性能。

数据库压测的价值不在跑分本身,而在把模糊的”应该没问题”变成可量化、可复现、可回归的基线。下次扩容或上索引前,先让 sysbench 和 pgbench 帮你说话。

上一篇 Kubernetes RBAC 实战:用 ServiceAccount 与 Role 实现最小权限
下一篇 ShellCheck 实战:用静态分析把 Bash 脚本 Bug 挡在运行前
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]