在使用pgbench对PostgreSQL进行基准测试时,最常被提及的两个结果是TPS与延迟。TPS是Transactions Per Second的缩写,表示每秒成功提交的事务数量;延迟则是每个事务从客户端发出到收到确认所经历的时间。很多人在看报告时只盯着TPS越高越好,却忽略了延迟的分布情况,导致线上系统在压测漂亮的情况下依然出现卡顿。

pgbench如何统计TPS与延迟
pgbench在运行结束时会打印出类似“tps = 1250.345678 (including connections establishing)”以及“latency average = 0.800 ms”这样的行。TPS的计算方式是用总完成事务数除以测试总时长,这里的时长默认包含连接建立阶段,如果使用-C参数每次事务新建连接,那么TPS会被建连开销显著拉低。延迟的平均值则是所有事务延迟的算术平均,pgbench还会给出标准差(latency stddev)来描述波动程度。
在源码层面,pgbench客户端会在每个事务开始前记录时间戳,事务提交后再次取时,差值就是该事务延迟。所有线程的延迟被汇总到主进程,最终算出平均与标准差。需要注意的是,pgbench默认的“事务”是由内置脚本定义的,例如pgbench自带的tpcb-like脚本包含多条SQL,因此这里的TPS并非单条SQL的每秒执行次数,而是完整业务脚本的吞吐。
如果我们自定义脚本,比如只执行一条主键查询,那么同样TPS下系统承载的SQL量会完全不同。因此在对比不同文章的pgbench结果时,必须先确认使用的脚本与并发数。下面是一段简单的自定义脚本与调用方式:
-- 自定义脚本 query.sql set id random(1, 100000) SELECT * FROM accounts WHERE id = :id;
pgbench -c 16 -j 4 -T 60 -f query.sql -n mydb
TPS与延迟之间的关系及误区
直观上TPS和延迟近似成反比:如果平均延迟是1毫秒,那么单线程理论TPS上限约1000。但数据库是多并发系统,当客户端并发数-c增加时,总TPS会上升,而平均延迟通常也会随之增加,因为请求开始排队。很多人误以为“延迟低就一定TPS高”,实际上在单连接下延迟极低但TPS也上不去,无法反映真实业务的多并发特征。
另一个常见误区是只看平均延迟。pgbench给出的latency average掩盖了长尾问题,若标准差很大,说明部分事务延迟可能是平均值的几十倍。这种毛刺在OLTP系统中往往对应锁等待或检查点落盘。我们可以通过--latency-limit=ms参数让超过阈值的事务被计入“late”统计,从而观察SLA达标率,而不是仅仅盯着平均线。
以下表格列出不同并发下某次测试的典型数值,可见TPS并未随并发线性增长,而延迟上升明显:
| 并发数 | TPS | 平均延迟(ms) | 延迟标准差(ms) |
|---|---|---|---|
| 4 | 3200 | 1.25 | 0.4 |
| 16 | 9800 | 1.63 | 1.2 |
| 64 | 12500 | 5.10 | 8.7 |
| 128 | 12800 | 10.02 | 22.5 |
如何利用指标定位性能瓶颈
当pgbench结果显示TPS达不到预期且延迟标准差巨大时,第一步应检查系统资源。通过top或iostat观察CPU是否饱和、磁盘await是否过高。如果CPU空闲但TPS上不去,往往是锁竞争或事务冲突,例如默认tpcb脚本在高并发下对branches表更新会触发行锁。此时平均延迟中的大部分可能消耗在等待而非执行。
延迟指标还能辅助判断网络开销。若数据库与pgbench客户端不在同一主机,平均延迟会包含RTT。使用-h 127.0.0.1本地连接可排除网络,若本地延迟依旧高,则问题在数据库内部。我们还可以在pgbench运行时开启log_min_duration_statement记录慢查询,交叉比对延迟毛刺对应的SQL。
对于写密集场景,TPS受WAL写入速度限制。当延迟随TPS接近磁盘顺序写上限而陡增,说明该机器不适合更高吞吐。此时考虑组提交参数commit_delay与commit_siblings来合并WAL刷盘,能在一定范围内提升TPS并平滑延迟。示例调整如下:
-- 在 postgresql.conf 中调整 commit_delay = 10 # 毫秒,尝试等待合并提交 commit_siblings = 5 # 至少有这么多并发事务才延迟
重新跑pgbench后,若延迟标准差下降且TPS小幅提升,即证明瓶颈在WAL刷盘频率。反之若毫无变化,则应排查其他子系统。只有将TPS与延迟联合起来看,才能避免被单一数字误导,做出正确的扩容或调优决策。