# 最小 pgbench 工作负载

LLMS 索引： [llms.txt](/llms.txt)

---

`pgbench` 既能运行内置类 TPC-B 工作负载，也能执行自定义事务脚本。本节只用它验证“连接—变量—事务—查询—结果采集”链路；20 次 tiny 查询不足以评价 PostgreSQL、Pigsty、硬件或参数。

## 2.5.1 初始化自定义脚本与参数 {#item-2-5-1}

`pgbench -i` 会创建它自己的 `pgbench_accounts`、`pgbench_branches` 等内置基准表。本章不使用这些对象；我们的“初始化”是先运行 [`setup.sql`](/labs/ch02/setup.sql)，得到 100 行确定性夹具，再运行[自定义工作负载](/labs/ch02/workload.sql)：

```text
\set fixture_id random(1, 100)
BEGIN;
SET LOCAL ROLE pg36_owner;
SELECT payload
FROM shop.ch02_fixture
WHERE fixture_id = :fixture_id;
COMMIT;
```

`pgbench` 脚本的 `\set` 与 `psql` 元命令不是同一套完整语言。这里调用 pgbench 的 `random(min, max)` 表达式，把结果存为变量；SQL 中 `:fixture_id` 再被替换为整数。

自定义脚本不会自动包裹事务。显式 `BEGIN`/`COMMIT` 让“一次 pgbench transaction”对应一次数据库事务。`SET LOCAL ROLE` 只在该事务内切换为对象 owner，提交后自动恢复；它服务于本章管理员直连实验，不是应用运行时的推荐身份。

先确认夹具：

```bash
psql -X -w "service=pg36-admin" \
  -v ON_ERROR_STOP=1 \
  -f verify.sql
```

再运行：

```bash
pgbench \
  --random-seed=20260729 \
  --no-vacuum \
  --client=1 \
  --jobs=1 \
  --transactions=20 \
  --report-per-command \
  --file=workload.sql \
  "service=pg36-admin application_name=pg36-ch02-pgbench"
```

参数意图：

| 参数 | 本章取值 | 原因 |
|---|---:|---|
| `--random-seed` | `20260729` | 固定单线程随机选择序列；放在前面确保覆盖所有随机用途 |
| `--no-vacuum` | 开启 | 不去处理不存在的内置 pgbench 表 |
| `--client` | `1` | 避免并发调度干扰教学输入 |
| `--jobs` | `1` | 保持一个工作线程 |
| `--transactions` | `20` | 让验收快速、计数精确 |
| `--report-per-command` | 开启 | 观察脚本各命令是否执行 |
| `--file` | 自定义脚本 | 不运行默认内置业务 |

这里通过 Pigsty `default` 服务 `5436`，路径是 HAProxy 到当前主库 PostgreSQL，不经过 PgBouncer。若改用 `primary` 服务 `5433`，必须先确认登录角色已安全配置密码并进入 PgBouncer 用户清单。两次结果属于不同连接路径，不能直接混为一个基准。

`pgbench` 客户端版本应与清单一并保存。自定义脚本语法和输出字段会随 PostgreSQL 版本演进；不要从另一台机器拿一个未知版本客户端就假设完全等价。

## 2.5.2 区分吞吐、延迟、错误与环境噪声 {#item-2-5-2}

一次正确运行的关键输出类似：

```text
number of clients: 1
number of threads: 1
number of transactions per client: 20
number of transactions actually processed: 20/20
number of failed transactions: 0 (0.000%)
latency average = ...
initial connection time = ...
tps = ... (without initial connection time)
```

本章真正验收的是：

- 客户端、线程与事务数符合命令；
- `20/20` 个事务实际完成；
- failed transactions 为 `0`；
- 标准错误中没有连接、SQL 或变量错误；
- 运行前后夹具校验和一致，因为工作负载只读。

其余数字需要先理解口径：

| 指标 | 回答的问题 | 不能单独回答什么 |
|---|---|---|
| TPS | 该脚本在当前运行条件下每秒完成多少事务 | 单条业务请求能力、生产容量 |
| 平均延迟 | 客户端观察到的平均事务时间 | 尾延迟、每条 SQL 的服务端执行时间 |
| initial connection time | 建立测试连接所需时间 | 长连接应用的稳态延迟 |
| per-command latency | 脚本每类命令的客户端耗时 | 并发下每次调用的完整分布 |
| failed transactions | pgbench 判定失败的事务数 | 所有业务错误和数据正确性 |

固定事务数时，测试持续时间很短，任何一次调度抖动都可能大幅改变 TPS。平均值还会隐藏最慢请求；正式测试至少需要时长、延迟分布、错误分类、预热和重复运行。

### 噪声从哪里来

即使脚本与种子完全相同，结果仍可能受到：

- 客户端 CPU、时钟与 pgbench 版本；
- DNS、网络、HAProxy 与是否经过 PgBouncer；
- PostgreSQL 数据页和操作系统页缓存；
- autovacuum、检查点、日志、备份与其他会话；
- 虚拟机 steal、CPU 频率、NUMA 和磁盘队列；
- 监控采样与终端输出；
- 第一次连接和第一次执行的初始化成本。

“第二次更快”经常只是缓存变热；“经连接池更慢”可能只是路径、认证和测量窗口不同。没有对照、重复和环境清单时，不应把相关性写成因果。

### 错误是一级指标

不要为追求 TPS 把错误行藏起来。若使用 `--latency-limit`，晚于阈值的事务会单独计数；若启用可重试错误处理，还要区分原始失败、重试和最终失败。一个吞吐更高但超时或失败更多的结果通常更差。

本章没有注入并发错误，因此只要求零失败。ch10 会制造隔离与冲突，ch26 才建立完整性能报告。

## 2.5.3 本节只建立可复现基线，不做性能结论 {#item-2-5-3}

这里的“基线”指可重放的**工作负载定义**，不是可外推的性能基线。我们冻结了：

- 数据集：100 行、确定公式、固定校验和；
- 查询：按 1–100 的 ID 读取一行 payload；
- 事务：显式 `BEGIN`、一次查询、`COMMIT`；
- 随机输入：单客户端、单线程、固定 seed；
- 运行量：20 个事务；
- 连接路径：清单中命名的 Pigsty service；
- 成功条件：20/20、零失败、数据摘要不变。

我们没有冻结操作系统调度、CPU、缓存、网络或后台活动，因此绝不写“应达到 N TPS”。读者在本地实测看到几百、几千或几万 TPS，都只能说明这个 tiny 任务在那个瞬间的观察值。

### 做一次反证

连续运行两次同一命令：

```bash
for run_id in 1 2; do
  pgbench \
    --random-seed=20260729 \
    -n -c 1 -j 1 -t 20 -r \
    -f workload.sql \
    "service=pg36-admin application_name=pg36-ch02-run-${run_id}" \
    >"evidence/ch02/pgbench-${run_id}.txt" \
    2>"evidence/ch02/pgbench-${run_id}.stderr"
done
```

两个文件应具有相同事务数与零失败，但 latency 和 TPS 通常不会完全相同。这正好证明“确定输入”与“确定耗时”是两件事。

若两次选择的 fixture ID 也需要逐项核对，可以让工作负载把 ID 写入单独的审计结果；但写日志本身会改变测量。任何观测都会有成本，测试设计要说明成本是否在比较双方中一致。

### 何时才允许谈性能

到 ch26，至少补齐：

1. 明确问题：容量、回归、极限还是组件对比；
2. 代表性数据量与事务比例；
3. 预热、持续时间、并发阶梯与重复次数；
4. 硬件、内核、容器/虚拟化和存储清单；
5. 客户端是否成为瓶颈；
6. 平均值、分位数、错误、饱和指标和置信边界；
7. 数据库、系统与 Pigsty 监控证据；
8. 测试后状态和可复位性。

本章的小负载只是让未来这些测试拥有一个已经验证的入口。

### 本节验收

- 能解释为什么自定义脚本仍需显式事务；
- 能说明 `--random-seed` 固定什么、不固定什么；
- 运行结果为 20/20 且零失败；
- 运行前后 `verify.sql` 校验和一致；
- 报告不把某次 TPS 当作 PostgreSQL 或 Pigsty 性能承诺。

## 参考资料

- [PostgreSQL 18：pgbench](https://www.postgresql.org/docs/18/pgbench.html)
- [PostgreSQL 18：pgbench 自定义脚本](https://www.postgresql.org/docs/18/pgbench.html#TRANSACTIONS-AND-SCRIPTS)
- [PostgreSQL 18：pgbench 随机种子](https://www.postgresql.org/docs/18/pgbench.html#PGBENCH-OPTION-RANDOM-SEED)
- [Pigsty v4.5：服务端点](https://pigsty.io/docs/pgsql/service/)

---

[上一节：输入、输出与确定性数据](../04/) · [返回本章目录](../) · [下一节：最小逻辑备份闭环](../06/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
