↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3

pgbench

pgbench — 运行 PostgreSQL 基准测试

大纲

pgbench -i [option...] [dbname]

pgbench [option...] [dbname]

描述

pgbench 是一个用于对 PostgreSQL 执行基准测试的简单程序。它会反复执行同一组 SQL 命令,也可在多个并发数据库会话中运行,然后计算平均事务速率(每秒事务数)。默认情况下,pgbench 测试的是一个大体上基于 TPC-B 的场景,每个事务包含五条 SELECT、UPDATE 和 INSERT 命令。不过,通过编写自己的事务脚本文件,也很容易测试其他场景。

典型的 pgbench 输出如下:

transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 10
number of threads: 1
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
number of failed transactions: 0 (0.000%)
latency average = 11.013 ms
latency stddev = 7.351 ms
initial connection time = 45.758 ms
tps = 896.967014 (without initial connection time)

前七行给出了若干最重要的参数设置。第六行报告了出现串行化或死锁错误时事务允许的最大尝试次数(更多信息见失败和串行化/死锁重试)。第八行报告实际完成的事务数和预期事务数(后者仅为客户端数量与每个客户端事务数的乘积);除非运行在完成前失败,或者某些 SQL 命令执行失败,否则两者应当相等。(在 -T 模式下,只打印实际事务数。)下一行报告因串行化或死锁错误而失败的事务数(更多信息见失败和串行化/死锁重试)。最后一行报告每秒事务数。

默认的类 TPC-B 事务测试要求预先建立特定的表。应使用 -i(初始化)选项调用 pgbench 来创建并填充这些表。(测试自定义脚本时不需要这一步,但需要自行完成测试所需的准备工作。)初始化命令如下:

pgbench -i [ other-options ] dbname

其中 dbname 是已创建好的、用于执行测试的数据库名称。(可能还需要使用 -h、-p 和/或 -U 选项来指定如何连接到数据库服务器。)

小心

pgbench -i 会创建四个表 pgbench_accounts、pgbench_branches、pgbench_history 和 pgbench_tellers,并销毁任何已存在的同名表。如果数据库中已经存在这些名称的表,请务必改用其他数据库!

在默认的“比例因子” 1 下,这些表最初包含如下行数:

table                   # of rows
---------------------------------
pgbench_branches        1
pgbench_tellers         10
pgbench_accounts        100000
pgbench_history         0

可以使用 -s(比例因子)选项来增加行数,而且在大多数场景下通常也应该这样做。此时还可以配合使用 -F(fillfactor)选项。

完成必要的准备后,就可以使用不带 -i 的命令运行基准测试,也就是:

pgbench [ options ] dbname

几乎在所有情况下,都需要附加一些选项才能得到有意义的测试。最重要的选项是 -c(客户端数)、-t(事务数)、-T(时间限制)以及 -f(指定自定义脚本文件)。完整列表见下文。

选项

以下内容分为三个小节。数据库初始化和运行基准测试时使用不同的选项,但有些选项在这两种情况下都适用。

初始化选项

pgbench 接受以下命令行初始化参数:

dbname #

指定要测试的数据库名称。如果未指定,则使用环境变量 PGDATABASE。如果未设置该变量,则使用连接指定的用户名。

-i
--initialize #

进入初始化模式所必需。

-I init_steps
--init-steps=init_steps #

仅执行正常初始化步骤中的选定部分。init_steps 指定要执行的初始化步骤,每个步骤用一个字符表示。各步骤会按照指定顺序调用。默认值为 dtgvp。可用步骤如下:

d(删除)#

删除任何现有的 pgbench 表。

t(创建表)#

创建标准 pgbench 场景使用的表,即 pgbench_accounts、pgbench_branches、pgbench_history 和 pgbench_tellers。

g 或 G(在客户端或服务器端生成数据)#

生成数据并将其装载到标准表中,替换其中任何已有数据。

使用 g(客户端生成数据)时,数据由 pgbench 客户端生成,再通过 COPY 发送到服务器,因此会大量占用客户端/服务器带宽。对于 14 及以上版本的 PostgreSQL,除非启用了分区,pgbench 会使用 FREEZE 选项,以加快后续的 VACUUM。使用 g 时,在为 pgbench_accounts 表生成数据的过程中,每生成 100,000 行会输出一条日志消息。

使用 G(服务器端生成数据)时,pgbench 客户端只发送较小的查询,随后实际数据在服务器端生成。这种方式不需要大量带宽,但服务器会承担更多工作。使用 G 时,生成数据期间不会打印任何进度消息。

默认初始化行为使用客户端生成数据(等同于 g)。

v(清理)#

在标准表上调用 VACUUM。

p(创建主键)#

在标准表上创建主键索引。

f(创建外键)#

在标准表之间创建外键约束。(请注意,默认情况下不执行此步骤。)

-F fillfactor
--fillfactor=fillfactor #

以给定的 fillfactor 创建 pgbench_accounts、pgbench_tellers 和 pgbench_branches 表。默认值为 100。

-n
--no-vacuum #

初始化期间不执行任何清理。(即使在 -I 中指定了 v 步骤,此选项也会抑制它。)

-q
--quiet #

将日志切换为静默模式,每 5 秒只输出一条进度消息。默认日志每生成 100,000 行输出一条消息,因而往往会在每秒输出多行(尤其是在较好的硬件上)。

如果在 -I 中指定了 G,则此设置无效。

-s scale_factor
--scale=scale_factor #

将生成的行数乘以比例因子。例如,-s 100 会在 pgbench_accounts 表中创建 10,000,000 行。默认值为 1。当比例达到 20,000 或更大时,用于保存账户标识符的列(aid 列)将切换到使用更大的整数(bigint),以便容纳账户标识符的取值范围。

--foreign-keys #

在标准表之间创建外键约束。(如果初始化步骤序列中尚未包含 f 步骤,则此选项会把它加入进去。)

--index-tablespace=index_tablespace #

在指定的表空间中创建索引,而不是默认的表空间。

--partition-method=NAME #

使用 NAME 方法创建分区的 pgbench_accounts 表。预期值为 range 或 hash。此选项要求 --partitions 设置为非零值。若未指定,默认为 range。

--partitions=NUM #

创建带有 NUM 个大小近乎相等分区的 pgbench_accounts 分区表,这些分区用于容纳按比例扩展后的账户数。默认值为 0,表示不分区。

--tablespace=tablespace #

在指定的表空间中创建表,而不是默认的表空间。

--unlogged-tables #

将所有表创建为不记录 WAL 的表,而不是永久表。

基准测试选项

pgbench 接受以下命令行基准测试参数:

-b scriptname[@weight]
--builtin=scriptname[@weight] #

将指定的内置脚本添加到待执行脚本列表中。可用的内置脚本包括:tpcb-like、simple-update 和 select-only。也接受内置名称的无歧义前缀。使用特殊名称 list 时,会显示内置脚本列表并立即退出。

可选地,可在@后写一个整数权重,以调整此脚本相对于其他脚本的选中概率。默认权重为 1。详情请参见下文。

-c clients
--client=clients #

模拟的客户端数量,也就是并发数据库会话的数量。默认值为 1。

-C
--connect #

为每个事务建立一个新连接,而不是仅在每个客户端会话中执行一次。这对于测量连接开销很有用。

-d
--debug #

打印调试输出。

-D varname=value
--define=varname=value #

定义一个变量,供自定义脚本使用(见下文)。允许使用多个 -D 选项。

-f filename[@weight]
--file=filename[@weight] #

将从 filename 读取的事务脚本添加到要执行的脚本列表中。

可选地,可在@后写一个整数权重,以调整此脚本相对于其他脚本的选中概率。默认权重为 1。(如果脚本文件名本身包含@字符,可追加一个权重以消除歧义,例如 filen@me@1。)详情见下文。

-j threads
--jobs=threads #

pgbench 中的工作线程数。在多 CPU 机器上使用多个线程可能会有所帮助。客户端尽可能均匀地分布在可用线程中。默认值为 1。

-l
--log #

将每个事务的信息写入日志文件。详情见下文。

-L limit
--latency-limit=limit #

持续时间超过 limit 毫秒的事务会被单独计数和报告,称为 late。

使用限流(--rate=...)时,若某个事务落后于计划时间超过 limit 毫秒,从而已经不可能满足延迟限制,则它根本不会被发送到服务器。此类事务会被单独计数并报告为 skipped。

使用 --max-tries 选项时,若某个事务因串行化异常或死锁而失败,且其所有尝试的总耗时大于 limit 毫秒,则不会再重试。若只想限制尝试总时间而不限制尝试次数,请使用 --max-tries=0。默认情况下,--max-tries 为 1,出现串行化/死锁错误的事务不会重试。有关此类事务重试的更多信息,见失败和串行化/死锁重试。

-M querymode
--protocol=querymode #

用于向服务器提交查询的协议:

  • simple:使用简单查询协议。

  • extended:使用扩展查询协议。

  • prepared:使用带有预备语句的扩展查询协议。

在 prepared 模式下,pgbench 从第二次查询迭代开始复用解析分析结果,因此 pgbench 比其他模式运行得更快。

默认值是简单查询协议。(更多信息见第 55 章。)

-n
--no-vacuum #

在运行测试前不执行任何清理。如果运行的是不包含标准表 pgbench_accounts、pgbench_branches、pgbench_history 和 pgbench_tellers 的自定义测试场景,则此选项是必需的。

-N
--skip-some-updates #

运行内置的 simple-update 脚本。是 -b simple-update 的简写。

-P sec
--progress=sec #

每 sec 秒显示一次进度报告。报告包括自运行开始以来的时间、自上次报告以来的 TPS、自上次报告以来事务延迟的平均值和标准差,以及失败事务数。使用限流(-R)时,延迟是相对于事务计划开始时间计算的,而不是实际开始时间,因此其中也包含平均计划滞后时间。当使用 --max-tries 启用事务在串行化/死锁错误后的重试时,报告还会包含发生过重试的事务数以及总重试次数。

-r
--report-per-command #

在基准测试完成后,报告每条命令的以下统计信息:平均延迟(从客户端视角看到的语句执行时间)、失败次数,以及该命令因串行化或死锁错误而发生的重试次数。仅当 --max-tries 选项不等于 1 时,报告才会显示重试统计信息。

-R rate
--rate=rate #

以指定速率执行事务,而不是像默认行为那样尽可能快地运行。速率以每秒事务数表示。如果目标速率高于可达到的最大速率,则速率限制不会影响结果。

该速率通过让事务沿着一条符合泊松分布的时间线启动来实现。预期开始时间表是根据客户端首次启动的时间向前推进的,而不是根据前一个事务结束的时间。这意味着,当某些事务超过其原定结束时间时,后续事务仍有可能重新赶上计划。

启用限流后,运行结束时报告的事务延迟是从计划开始时间计算的,因此它包含每个事务等待前一个事务完成的时间。这段等待时间称为计划滞后时间,其平均值和最大值也会单独报告。若要得到相对于事务实际开始时间的延迟,也就是事务在数据库中实际执行所花费的时间,可以用报告中的延迟减去计划滞后时间。

如果同时使用 --latency-limit 和 --rate,一个事务可能会落后太多,以至于在前一个事务结束时已经超过了延迟限制,因为延迟是从计划开始时间计算的。这样的事务不会发送到服务器,而是完全跳过并单独计数。

较高的计划滞后时间表明,在所选客户端数和线程数下,系统无法以指定速率处理事务。当平均事务执行时间长于事务之间的计划间隔时,后续事务会不断进一步落后,而计划滞后时间也会随着测试持续时间增加。在这种情况下,需要降低指定的事务速率。

-s scale_factor
--scale=scale_factor #

在 pgbench 输出中报告指定的比例因子。对于内置测试,这没有必要;系统会通过统计 pgbench_branches 表中的行数来检测正确的比例因子。但在只测试自定义基准(-f 选项)时,除非使用此选项,否则比例因子会被报告为 1。

-S
--select-only #

运行内置的 select-only 脚本。是 -b select-only 的简写。

-t transactions
--transactions=transactions #

每个客户端运行的事务数量。默认值为 10。

-T seconds
--time=seconds #

让测试运行指定的秒数,而不是让每个客户端执行固定数量的事务。-t 和 -T 是互斥的。

-v
--vacuum-all #

在运行测试之前,对四个标准表全部执行清理。如果既不使用 -n 也不使用 -v,pgbench 会对 pgbench_tellers 和 pgbench_branches 表进行清理,并截断 pgbench_history。

--aggregate-interval=seconds #

聚合间隔的长度(以秒为单位)。只能与 -l 选项一起使用。使用此选项时,日志包含每个间隔的摘要数据,如下所述。

--failures-detailed #

在逐事务日志、聚合日志以及主报告和逐脚本报告中,按以下类型分组报告失败:

  • 串行化失败;

  • 死锁失败;

更多信息见失败和串行化/死锁重试。

--log-prefix=prefix #

设置由 --log 创建的日志文件的文件名前缀。默认值为 pgbench_log。

--max-tries=number_of_tries #

启用对出现串行化/死锁错误的事务进行重试,并设置最大尝试次数。此选项可与 --latency-limit 组合使用,后者限制同一事务所有尝试的总耗时;此外,如果没有 --latency-limit 或 --time,则不能使用无限次尝试(--max-tries=0)。默认值为 1,出现串行化/死锁错误的事务不会重试。有关此类事务重试的更多信息,见失败和串行化/死锁重试。

--progress-timestamp #

显示进度(选项 -P)时,使用时间戳(Unix 纪元)而不是自运行开始以来的秒数。单位为秒,小数点后精确到毫秒。这有助于比较各种工具生成的日志。

--random-seed=seed #

设置随机数生成器种子。它会先为系统随机数生成器设种,再生成一系列初始生成器状态,每个线程一个。seed 的取值可以是:time(默认值,种子基于当前时间),rand(使用强随机源,如果没有可用的则失败),或者无符号十进制整数值。随机生成器既可以在 pgbench 脚本中显式调用(random...函数),也可以隐式调用(例如选项 --rate 用于调度事务)。显式设置时,实际用于设种的值会显示在终端上。任何允许的 seed 值也可以通过环境变量 PGBENCH_RANDOM_SEED 提供。为确保提供的种子影响所有可能的用途,将此选项放在第一位或使用环境变量。

显式设置种子可以在随机数层面精确复现一次 pgbench 运行。由于随机状态按线程管理,这意味着对于相同的调用,如果每个线程只有一个客户端,且不存在外部依赖或数据依赖,那么 pgbench 运行可以完全一致。从统计角度看,精确复现实验并不是好主意,因为它可能掩盖性能波动,甚至不恰当地提高性能,例如反复命中与前一次运行相同的页面。不过,这对调试也可能非常有帮助,例如重现会触发错误的棘手案例。请谨慎使用。

--sampling-rate=rate #

写入日志时使用的采样率,用于减少生成的日志量。如果给定此选项,则只记录指定比例的事务。1.0 表示记录全部事务,0.05 表示只记录 5% 的事务。

处理日志文件时,记得把采样率考虑进去。例如,计算 TPS 值时,需要按采样率对数字进行相应换算(例如采样率为 0.01 时,只能得到实际 TPS 的 1/100)。

--show-script=scriptname #

将内置脚本 scriptname 的实际代码输出到 stderr,然后立即退出。

--verbose-errors #

打印关于所有错误和失败(不再重试的错误)的消息,包括超出了哪一种重试限制,以及对于串行化/死锁失败超出的幅度。(请注意,这种情况下输出量可能会显著增加。)更多信息见失败和串行化/死锁重试。

公共选项

pgbench 还接受以下用于连接参数的通用命令行参数:

-h hostname
--host=hostname #

数据库服务器的主机名。

-p port
--port=port #

数据库服务器的端口号。

-U login
--username=login #

连接时使用的用户名。

-V
--version #

打印 pgbench 版本并退出。

-?
--help #

显示 pgbench 命令行参数的帮助信息并退出。

退出状态

成功运行会以状态 0 退出。状态 1 表示静态问题,例如无效的命令行选项,或者按理不应发生的内部错误。在启动基准测试时发生的早期错误,例如初始连接失败,也会以状态 1 退出。运行过程中出现的错误,例如数据库错误或脚本中的问题,会导致退出状态 2。在后一种情况下,pgbench 会打印部分结果。

环境

PGDATABASE
PGHOST
PGPORT
PGUSER #

默认连接参数。

与大多数其他 PostgreSQL 工具一样,此工具也使用 libpq 支持的环境变量(见第 34.15 节)。

环境变量 PG_COLOR 指定是否在诊断消息中使用颜色。可能的值为 always、auto 和 never。

注解

在 pgbench 中实际执行的“事务”是什么?

pgbench 会从指定列表中随机选取测试脚本来执行。这些脚本既可以是用 -b 指定的内置脚本,也可以是用 -f 指定的用户脚本。每个脚本都可以在其后加上一个以@引出的相对权重,以改变其被选中的概率。默认权重为 1。权重为 0 的脚本会被忽略。

默认的内置事务脚本(也可通过 -b tpcb-like 调用)会针对随机选取的 aid、tid、bid 和 delta 在每个事务中发出七条命令。该场景受 TPC-B 基准启发,但并不是真正的 TPC-B,因此才取了这个名字。

  1. BEGIN;

  2. UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;

  3. SELECT abalance FROM pgbench_accounts WHERE aid = :aid;

  4. UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;

  5. UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;

  6. INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);

  7. END;

如果选择 simple-update 内置脚本(也就是 -N),事务中将不包含第 4 步和第 5 步。这会避免在这些表上发生更新争用,但也会让该测试场景更不像 TPC-B。

如果选择 select-only 内置脚本(也就是 -S),则只执行 SELECT。

自定义脚本

pgbench 支持自定义基准测试场景:只需用从文件中读取的事务脚本(-f 选项)替换默认事务脚本(见上文)即可。在这种情况下,一个“事务”就表示脚本文件的一次执行。

脚本文件包含一个或多个以分号结束的 SQL 命令。空行以及以--开头的行会被忽略。脚本文件还可以包含“元命令”,它们由 pgbench 自身解释,详见下文。

注意

在 PostgreSQL 9.6 之前,脚本文件中的 SQL 命令以换行结束,因此不能跨行。现在连续 SQL 命令之间必须用分号分隔(如果 SQL 命令后面跟着一个元命令,则不需要分号)。如果需要创建一个既能在旧版也能在新版 pgbench 下工作的脚本文件,务必将每个 SQL 命令写在单独一行,并以分号结束。

假定 pgbench 脚本不包含不完整的 SQL 事务块。如果在运行时客户端在尚未完成最后一个事务块时就到达脚本末尾,该客户端将被中止。

脚本文件提供了一种简单的变量替换机制。变量名必须由字母(包括非拉丁字母)、数字和下划线组成,并且首字符不能是数字。如上所述,变量可以用命令行 -D 选项设置,也可以用下文介绍的元命令设置。除了通过 -D 命令行选项预先设置的变量之外,还有少量变量会被自动预设,列在表 288 中。如果使用 -D 为这些变量指定值,则会覆盖自动预设值。一旦设置好变量,就可以在 SQL 命令中写:variablename 来插入其值。当运行多个客户端会话时,每个会话都有自己的变量集合。pgbench 在单条语句中最多支持 255 次变量使用。

表 288. pgbench 自动变量

变量描述
client_id 标识客户端会话的唯一编号(从零开始)
default_seed 默认在 hash 和伪随机置换函数中使用的种子
random_seed 随机数生成器种子(除非被 -D 覆盖)
scale 当前比例因子

脚本文件中的元命令以反斜线(\)开头,通常延伸到行尾,不过也可以通过写反斜线换行继续到后续行。元命令的参数以空白分隔。支持的元命令如下:

\gset [prefix] \aset [prefix] #

这些命令可用于结束 SQL 查询,以代替结尾分号(;)。

使用\gset 命令时,前面的 SQL 查询预期返回一行,其各列会被存入以列名命名的变量;如果提供了 prefix,则会加上该前缀。

使用\aset 命令时,所有组合 SQL 查询(由\;分隔)的列都会被存入以列名命名的变量中;如果提供了 prefix,则会加上该前缀。如果查询不返回任何行,则不会进行赋值,可以通过测试变量是否存在来检测这种情况。如果查询返回多行,则保留最后一个值。

\gset 和 \aset 不能用在管道模式,因为在命令需要它们的时候,查询结果还不可用。

下面的示例将第一个查询中的最终账户余额放入变量 abalance,并用第三个查询中的整数填充变量 p_two 和 p_three。第二个查询的结果会被丢弃。最后两个组合查询的结果会被存入变量 four 和 five。

UPDATE pgbench_accounts
  SET abalance = abalance + :delta
  WHERE aid = :aid
  RETURNING abalance \gset
-- 两个查询的组合
SELECT 1 \;
SELECT 2 AS two, 3 AS three \gset p_
SELECT 4 AS four \; SELECT 5 AS five \aset
\if expression
\elif expression
\else
\endif #

这一组命令实现了可嵌套的条件块,类似于 psql 中的\if expression。条件表达式与\set 中的表达式相同,非零值会被解释为真。

\set varname expression #

将变量 varname 设置为根据 expression 计算出的值。该表达式可以包含 NULL 常量、布尔常量 TRUE 和 FALSE、5432 这样的整数常量、3.14159 这样的双精度常量、对变量的引用:variablename、操作符(保留其通常的 SQL 优先级和结合性)、函数调用、SQL CASE 通用条件表达式以及括号。

函数和大部分操作符在 NULL 输入上会返回 NULL。

就条件判断而言,非零数值为 TRUE,零值和 NULL 为 FALSE。

过大或过小的整数和双精度常量,以及整数算术操作符(+、-、* 和/)都会在溢出时引发错误。

在没有为 CASE 提供最终的 ELSE 子句时,默认值是 NULL。

示例:

\set ntellers 10 * :scale
\set aid (1021 * random(1, 100000 * :scale)) % \
           (100000 * :scale) + 1
\set divx CASE WHEN :x <> 0 THEN :y/:x ELSE NULL END
\sleep number [ us | ms | s ] #

让脚本执行休眠指定时长,单位可以是微秒(us)、毫秒(ms)或秒(s)。如果省略单位,则默认为秒。number 可以是整数常量,也可以是引用了整数值变量的:variablename。

示例:

\sleep 10 ms
\setshell varname command [ argument ... ] #

将变量 varname 设置为 shell 命令 command 在给定 argument 参数下的结果。该命令必须通过标准输出返回一个整数值。

command 和每个 argument 都可以是文本常量,也可以是引用某个变量的:variablename。如果要使用以冒号开头的 argument,请在 argument 开头再写一个冒号。

示例:

\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
\shell command [ argument ... ] #

与\setshell 相同,但命令结果会被丢弃。

示例:

\shell command literal_argument :variable ::literal_starting_with_colon
\startpipeline
\endpipeline #

这些命令界定 SQL 语句管道的开始和结束。在管道模式下,语句会发送到服务器,而不等待前一条语句的结果。更多细节见第 34.5 节。管道模式要求使用扩展查询协议。

内置操作符

表 289 中列出的算术、按位、比较和逻辑操作符都内置于 pgbench 中,可用于\set 中的表达式。这些操作符按优先级从低到高列出。除非另有说明,接受两个数字输入的操作符只要任一输入为双精度,就会产生双精度结果;否则产生整数结果。

表 289. pgbench 操作符

操作符

描述

示例

boolean OR boolean → boolean

逻辑或

5 or 0 → TRUE

boolean AND boolean → boolean

逻辑与

3 and 0 → FALSE

NOT boolean → boolean

逻辑非

not false → TRUE

boolean IS [NOT] (NULL|TRUE|FALSE) → boolean

布尔值测试

1 is null → FALSE

value ISNULL|NOTNULL → boolean

空值测试

1 notnull → TRUE

number = number → boolean

等于

5 = 4 → FALSE

number <> number → boolean

不等于

5 <> 4 → TRUE

number != number → boolean

不等于

5 != 5 → FALSE

number < number → boolean

小于

5 < 4 → FALSE

number <= number → boolean

小于等于

5 <= 4 → FALSE

number > number → boolean

大于

5 > 4 → TRUE

number >= number → boolean

大于等于

5 >= 4 → TRUE

integer | integer → integer

按位或

1 | 2 → 3

integer # integer → integer

按位异或

1 # 3 → 2

integer & integer → integer

按位与

1 & 3 → 1

~ integer → integer

按位非

~ 1 → -2

integer << integer → integer

按位左移

1 << 2 → 4

integer >> integer → integer

按位右移

8 >> 2 → 2

number + number → number

加法

5 + 4 → 9

number - number → number

减法

3 - 2.0 → 1.0

number * number → number

乘法

5 * 4 → 20

number / number → number

除法(如果两个输入都是整数,则将结果向零截断)

5 / 3 → 1

integer % integer → integer

模(余数)

3 % 2 → 1

- number → number

取相反数

- 2.0 → -2.0


内置函数

表 290 中列出的函数都内置于 pgbench,可用于\set 中的表达式。

表 290. pgbench 函数

函数

描述

示例

abs ( number ) → 与输入类型相同

绝对值

abs(-17) → 17

debug ( number ) → 与输入类型相同

将参数打印到 stderr,并返回参数。

debug(5432.1) → 5432.1

double ( number ) → double

转换为 double。

double(5432) → 5432.0

exp ( number ) → double

指数函数(e 的给定次幂)

exp(1.0) → 2.718281828459045

greatest ( number [, ... ] ) → 若任一参数为 double,则为 double,否则为 integer

选择参数中的最大值。

greatest(5, 4, 3, 2) → 5

hash ( value [, seed ] ) → integer

这是 hash_murmur2 的别名。

hash(10, 5432) → -5817877081768721676

hash_fnv1a ( value [, seed ] ) → integer

计算 FNV-1a hash。

hash_fnv1a(10, 5432) → -7793829335365542153

hash_murmur2 ( value [, seed ] ) → integer

计算 MurmurHash2 hash。

hash_murmur2(10, 5432) → -5817877081768721676

int ( number ) → integer

转换为 integer。

int(5.4 + 3.8) → 9

least ( number [, ... ] ) → 若任一参数为 double,则为 double,否则为 integer

选择参数中的最小值。

least(5, 4, 3, 2.1) → 2.1

ln ( number ) → double

自然对数

ln(2.718281828459045) → 1.0

mod ( integer, integer ) → integer

模(余数)

mod(54, 32) → 22

permute ( i, size [, seed ] ) → integer

i 的置换值,范围在[0, size) 内。它表示 i(模 size)在整数 0...size-1 的伪随机置换中的新位置;该置换由 seed 参数化,详见下文。

permute(0, 4) → 0至3之间的整数

pi () → double

π的近似值

pi() → 3.14159265358979323846

pow ( x, y ) → double

power ( x, y ) → double

x 的 y 次幂

pow(2.0, 10) → 1024.0

random ( lb, ub ) → integer

计算[lb, ub] 中的均匀分布随机整数。

random(1, 10) → 介于1和10之间的整数

random_exponential ( lb, ub, parameter ) → integer

计算[lb, ub] 中的指数分布随机整数,详见下文。

random_exponential(1, 10, 3.0) → 介于1和10之间的整数

random_gaussian ( lb, ub, parameter ) → integer

计算[lb, ub] 中的高斯分布随机整数,详见下文。

random_gaussian(1, 10, 2.5) → 介于1和10之间的整数

random_zipfian ( lb, ub, parameter ) → integer

计算[lb, ub] 中的 Zipfian 分布随机整数,详见下文。

random_zipfian(1, 10, 1.5) → 介于1和10之间的整数

sqrt ( number ) → double

平方根

sqrt(2.0) → 1.414213562


random 函数使用均匀分布生成值,也就是说,指定范围内的所有值都以相同概率被抽取。random_exponential、random_gaussian 和 random_zipfian 函数则需要额外提供一个 double 参数,用来确定分布的精确形状。

  • 对于指数分布,parameter 通过在 parameter 处截断一个快速衰减的指数分布,再将其投影到边界之间的整数上,从而控制分布。准确地说,令


    f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1 - exp(-parameter))

    则 min 和 max 之间(含边界)的值 i 会以 f(i) - f(i + 1) 的概率被抽中。

    直观地说,parameter 越大,越靠近 min 的值越容易被抽到,而越靠近 max 的值越不容易被抽到。parameter 越接近 0,分布就越平坦(也就越均匀)。对这种分布的一个粗略近似是:范围内出现频率最高的 1% 的值,即最靠近 min 的那些值,大约会占到 parameter% 的抽样次数。parameter 必须严格大于 0。

  • 对于高斯分布,该区间会映射到一个标准正态分布(经典钟形高斯曲线),并在左侧 -parameter 和右侧+parameter 处截断。区间中部的值更容易被抽到。准确地说,如果 PHI(x) 是标准正态分布的累积分布函数,均值 mu 定义为(max + min) / 2.0,则有


    f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) /
           (2.0 * PHI(parameter) - 1)

    则 min 和 max(包含边界)之间的值 i 被抽中的概率为:f(i + 0.5) - f(i - 0.5)。直观地说,parameter 越大,越靠近区间中间的值被抽到的频率越高,而越靠近 min 和 max 边界的值被抽到的频率越低。大约 67% 的值会落在区间中部 1.0 / parameter 这一段内,也就是均值两侧各占区间长度 0.5 / parameter 的范围内;约 95% 的值会落在区间中部 2.0 / parameter 这一段内,也就是均值两侧各占区间长度 1.0 / parameter 的范围内。例如,如果 parameter 为 4.0,则 67% 的值会落在区间中间四分之一(1.0 / 4.0)内,也就是从 3.0 / 8.0 到 5.0 / 8.0;95% 的值会落在区间中间一半(2.0 / 4.0)内,也就是第二和第三四分位。允许的最小 parameter 值为 2.0。

  • random_zipfian 会生成一个有界的 Zipfian 分布。parameter 定义该分布的倾斜程度。parameter 越大,越靠近区间起始处的值被抽到的频率就越高。这种分布满足:假设范围从 1 开始,抽到 k 与抽到 k+1 的概率之比为 ((k+1)/k)**parameter。例如,random_zipfian(1, ..., 2.5) 生成值 1 的频率大约是生成 2 的(2/1)**2.5 = 5.66 倍,而生成 2 的频率又大约是生成 3 的(3/2)**2.5 = 2.76 倍,依此类推。

    pgbench 的实现基于《Non-Uniform Random Variate Generation》,Luc Devroye,Springer 1986,第 550-551 页。受该算法限制,parameter 的取值范围被限制在 [1.001, 1000]。

注意

在设计以非均匀方式选择行的基准测试时,要注意被选中的行可能与其他数据相关,例如与序列生成的 ID 或物理行顺序相关,这可能会扭曲性能测量结果。

为避免这一点,可以使用 permute 函数,或采用其他具有类似效果的额外步骤,对选中的行进行打乱并移除此类相关性。

Hash 函数 hash、hash_murmur2 和 hash_fnv1a 都接受一个输入值和一个可选的种子参数。如果没有提供种子,则会使用:default_seed 的值;除非通过命令行 -D 选项覆盖,否则该值会被随机初始化。

permute 接受一个输入值、一个大小参数以及一个可选的种子参数。它会生成范围[0, size) 内整数的伪随机置换,并返回输入值在该置换中的索引。所选置换由种子参数决定;如果未指定种子,则默认使用:default_seed。与 hash 函数不同,permute 保证输出值中不会出现冲突或空洞。区间之外的输入值会按 size 取模解释。如果 size 不是正数,该函数会报错。permute 可用于打散诸如 random_zipfian 或 random_exponential 这类非均匀随机函数的分布,使那些更常抽到的值不再具有显而易见的相关性。例如,下面的 pgbench 脚本模拟了社交媒体和博客平台中一种可能的真实工作负载,其中少数账户会产生过量负载:

\set size 1000000
\set r random_zipfian(1, :size, 1.07)
\set k 1 + permute(:r, :size)

在某些情况下,需要若干彼此不相关的不同分布,这时可选种子参数就很有用:

\set k1 1 + permute(:r, :size, :default_seed + 123)
\set k2 1 + permute(:r, :size, :default_seed + 321)

也可以用 hash 近似实现类似的行为:

\set size 1000000
\set r random_zipfian(1, 100 * :size, 1.07)
\set k 1 + abs(hash(:r)) % :size

但是,由于 hash 会产生冲突,有些值将永远不可达,而另一些值则会比原始分布所预期的更常出现。

作为一个示例,内置的类 TPC-B 事务的全部定义是:

\set aid random(1, 100000 * :scale)
\set bid random(1, 1 * :scale)
\set tid random(1, 10 * :scale)
\set delta random(-5000, 5000)
BEGIN;
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;

该脚本允许事务的每次迭代都引用不同的随机选中行。(这个示例也说明了为什么每个客户端会话都必须拥有自己的变量 — 否则它们就无法彼此独立地访问不同的行。)

逐事务日志记录

使用 -l 选项时(但未指定 --aggregate-interval 选项),pgbench 会将每个事务的信息写入日志文件。日志文件名为 prefix.nnn,其中 prefix 默认为 pgbench_log,nnn 是 pgbench 进程的 PID。可以用 --log-prefix 选项修改此前缀。如果 -j 选项为 2 或更大,即存在多个工作线程,则每个工作线程都有自己的日志文件。第一个工作线程的日志文件名与标准单工作线程情况相同。其他工作线程的附加日志文件名为 prefix.nnn.mmm,其中 mmm 是每个工作线程从 1 开始的顺序编号。

日志文件的每一行描述一个事务。它包含以下以空格分隔的字段:

client_id

标识运行事务的客户端会话

transaction_no

统计该会话已执行的事务数量

time

事务耗时,单位为微秒

script_no

标识该事务所使用的脚本文件(当通过 -f 或 -b 指定多个脚本时很有用)

time_epoch

事务完成时间,以 Unix 纪元时间戳表示

time_us

事务完成时间的小数秒部分,以微秒为单位

schedule_lag

事务开始延迟,即事务计划开始时间与实际开始时间之间的差值,单位为微秒(仅在指定 --rate 时出现)

retries

该事务在发生串行化或死锁错误后的重试次数(仅当 --max-tries 不等于 1 时出现)

当同时使用 --rate 和 --latency-limit 时,跳过事务的 time 将被报告为 skipped。如果事务以失败结束,其 time 将被报告为 failed。如果使用 --failures-detailed 选项,失败事务的 time 会根据失败类型报告为 serialization 或 deadlock(详见失败和串行化/死锁重试)。

这里是在单个客户端运行中生成的一个日志文件的片段:

0 199 2241 0 1175850568 995598
0 200 2465 0 1175850568 998079
0 201 2513 0 1175850569 608
0 202 2038 0 1175850569 2663

另一个示例使用的是 --rate=100 以及 --latency-limit=5(注意额外的 schedule_lag 列):

0 81 4621 0 1412881037 912698 3005
0 82 6173 0 1412881037 914578 4304
0 83 skipped 0 1412881037 914578 5217
0 83 skipped 0 1412881037 914578 5099
0 83 4722 0 1412881037 916203 3108
0 84 4142 0 1412881037 918023 2333
0 85 2465 0 1412881037 919759 740

在这个示例中,事务 82 迟到了,因为它的延迟(6.173 ms)超过了 5 ms 限制。接下来的两个事务被跳过,因为它们在开始之前就已经迟到了。

以下示例显示了一个带有失败和重试的日志文件片段,最大尝试次数设置为 10(请注意额外的 retries 列):

3 0 47423 0 1499414498 34501 3
3 1 8333 0 1499414498 42848 0
3 2 8358 0 1499414498 51219 0
4 0 72345 0 1499414498 59433 6
1 3 41718 0 1499414498 67879 4
1 4 8416 0 1499414498 76311 0
3 3 33235 0 1499414498 84469 3
0 0 failed 0 1499414498 84905 9
2 0 failed 0 1499414498 86248 9
3 4 8307 0 1499414498 92788 0

如果使用 --failures-detailed 选项,失败的类型将在 time 中报告,如下所示:

3 0 47423 0 1499414498 34501 3
3 1 8333 0 1499414498 42848 0
3 2 8358 0 1499414498 51219 0
4 0 72345 0 1499414498 59433 6
1 3 41718 0 1499414498 67879 4
1 4 8416 0 1499414498 76311 0
3 3 33235 0 1499414498 84469 3
0 0 serialization 0 1499414498 84905 9
2 0 serialization 0 1499414498 86248 9
3 4 8307 0 1499414498 92788 0

在能够处理大量事务的硬件上运行长时间测试时,日志文件可能会变得非常大。可以使用 --sampling-rate 选项,仅记录事务的随机样本。

聚合日志记录

使用 --aggregate-interval 选项时,日志文件会采用不同的格式。每一行日志描述一个聚合时间间隔,包含以下以空格分隔的字段:

interval_start

该时间间隔的起始时间,以 Unix 纪元时间戳表示

num_transactions

该时间间隔内的事务数

sum_latency

事务延迟的总和

sum_latency_2

事务延迟的平方和

min_latency

最小事务延迟

max_latency

最大事务延迟

sum_lag

事务开始延迟的总和(除非指定了 --rate,否则为零)

sum_lag_2

事务开始延迟的平方和(除非指定了 --rate,否则为零)

min_lag

最小事务开始延迟(除非指定了 --rate,否则为零)

max_lag

最大事务开始延迟(除非指定了 --rate,否则为零)

skipped

因为启动时间会太晚而被跳过的事务数(除非指定了 --rate 和 --latency-limit,否则为零)

retried

发生过重试的事务数(除非 --max-tries 不等于 1,否则为零)

retries

串行化或死锁错误后的重试总次数(除非 --max-tries 不等于 1,否则为零)

serialization_failures

发生串行化错误且其后未再重试的事务数(除非指定了 --failures-detailed,否则为零)

deadlock_failures

发生死锁错误且其后未再重试的事务数(除非指定了 --failures-detailed,否则为零)

下面是使用这些选项生成的示例输出:

pgbench --aggregate-interval=10 --time=20 --client=10 --log --rate=1000 --latency-limit=10 --failures-detailed --max-tries=10 test

1650260552 5178 26171317 177284491527 1136 44462 2647617 7321113867 0 9866 64 7564 28340 4148 0
1650260562 4808 25573984 220121792172 1171 62083 3037380 9666800914 0 9998 598 7392 26621 4527 0

请注意,普通(未聚合)日志格式会显示每个事务所使用的脚本,而聚合格式不会。因此,如果需要按脚本区分的数据,就必须自行聚合。

逐语句报告

使用 -r 选项,pgbench 为每个语句收集以下统计信息:

报告仅在 --max-tries 选项不等于 1 时显示重试统计信息。

所有数值都是针对每个客户端执行的每条语句计算的,并在基准测试完成后报告。

对于默认脚本,输出将类似如下:

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
number of failed transactions: 0 (0.000%)
number of transactions above the 50.0 ms latency limit: 1311/10000 (13.110 %)
latency average = 28.488 ms
latency stddev = 21.009 ms
initial connection time = 69.068 ms
tps = 346.224794 (without initial connection time)
statement latencies in milliseconds and failures:
   0.012  0  \set aid random(1, 100000 * :scale)
   0.002  0  \set bid random(1, 1 * :scale)
   0.002  0  \set tid random(1, 10 * :scale)
   0.002  0  \set delta random(-5000, 5000)
   0.319  0  BEGIN;
   0.834  0  UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
   0.641  0  SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  11.126  0  UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  12.961  0  UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
   0.634  0  INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
   1.957  0  END;

使用可串行化默认事务隔离级别的默认脚本的另一个输出示例(PGOPTIONS='-c default_transaction_isolation=serializable' pgbench ...):

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
maximum number of tries: 10
number of transactions per client: 1000
number of transactions actually processed: 6317/10000
number of failed transactions: 3683 (36.830%)
number of transactions retried: 7667 (76.670%)
total number of retries: 45339
number of transactions above the 50.0 ms latency limit: 106/6317 (1.678 %)
latency average = 17.016 ms
latency stddev = 13.283 ms
initial connection time = 45.017 ms
tps = 186.792667 (without initial connection time)
statement latencies in milliseconds, failures and retries:
  0.006     0      0  \set aid random(1, 100000 * :scale)
  0.001     0      0  \set bid random(1, 1 * :scale)
  0.001     0      0  \set tid random(1, 10 * :scale)
  0.001     0      0  \set delta random(-5000, 5000)
  0.385     0      0  BEGIN;
  0.773     0      1  UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
  0.624     0      0  SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  1.098   320   3762  UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  0.582  3363  41576  UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
  0.465     0      0  INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
  1.933     0      0  END;

如果指定了多个脚本文件,则会分别为每个脚本文件报告所有统计信息。

注意,为逐语句延迟计算收集额外的计时信息会带来一定开销。这会拖慢平均执行速度,并降低计算出的 TPS。减速幅度在很大程度上取决于平台和硬件。比较启用和未启用延迟报告时的平均 TPS 值,是判断这一计时开销是否显著的好方法。

失败和串行化/死锁重试

在执行 pgbench 时,有三种主要类型的错误:

  • 主程序错误。它们最为严重,总是会导致 pgbench 立即退出,并显示相应的错误消息。它们包括:

    • pgbench 开始执行时的错误(例如选项值无效);

    • 初始化模式中的错误(例如,用于创建内置脚本所需表的查询失败);

    • 在线程启动之前发生的错误(例如无法连接到数据库服务器、元命令中有语法错误、线程创建失败);

    • 内部 pgbench 错误(理论上永远不该发生……)。

  • 线程在管理其客户端时发生的错误(例如,客户端无法开始连接数据库服务器,或客户端连接数据库服务器所用的套接字已经失效)。在这种情况下,该线程的所有客户端都会停止,而其他线程继续工作。

  • 直接客户端错误。只有在发生内部 pgbench 错误(理论上不应发生)时,它们才会导致 pgbench 立即退出并显示相应错误消息。否则,最坏情况下只会中止失败的客户端,而其他客户端继续运行(但某些客户端错误会在不中止客户端的情况下处理并单独报告,见下文)。本节后续默认讨论的都是直接客户端错误,而不是内部 pgbench 错误。

客户端在发生严重错误时会中止运行;例如,与数据库服务器的连接丢失,或者脚本在最后一个事务尚未完成时就结束了。另外,如果 SQL 或元命令执行失败,且原因不是串行化或死锁错误,客户端也会中止。否则,如果 SQL 命令因串行化或死锁错误而失败,客户端不会中止。在这种情况下,当前事务会回滚,也包括将客户端变量设置为此事务运行之前的状态(假设一个事务脚本只包含一个事务;详见在 pgbench 中实际执行的“事务”是什么?了解更多信息)。发生串行化或死锁错误的事务会在回滚后重新执行,直到成功完成,或者达到最大尝试次数(由 --max-tries 指定)、达到最大重试时间(由 --latency-limit 指定),或者基准测试结束(由 --time 指定)。如果最后一次尝试仍然失败,该事务会被报告为失败,但客户端不会中止,而是继续工作。

注意

不指定 --max-tries 选项时,事务在发生串行化或死锁错误后永远不会重试,因为其默认值为 1。可以使用无限次尝试(--max-tries=0)并配合 --latency-limit 选项,仅限制尝试的最长总时间。也可以使用 --time 选项,在无限次尝试的情况下限制基准测试持续时间。

在重复包含多个事务的脚本时要小心:脚本总是完全重试,因此成功的事务可能会执行多次。

使用 shell 命令重试事务时要小心。与 SQL 命令的结果不同,shell 命令的结果不会回滚,唯一的例外是\setshell 命令设置的变量值。

成功事务的延迟包括事务执行的整个时间,包括回滚和重试。延迟仅针对成功的事务和命令进行测量,而不针对失败的事务或命令。

主报告包含失败事务的数量。如果 --max-tries 选项不等于 1,主报告还会包含与重试相关的统计信息:发生过重试的事务总数以及重试总次数。每个脚本报告都会继承主报告中的这些字段。逐语句报告仅在 --max-tries 选项不等于 1 时显示重试统计信息。

如果希望在逐事务日志、聚合日志以及主报告和逐脚本报告中按基本类型对失败进行分组,请使用 --failures-detailed 选项。如果还希望按类型区分所有错误和失败(不再重试的错误),包括超出了哪一种重试限制,以及串行化/死锁失败超出了多少,请使用 --verbose-errors 选项。

良好实践

很容易用 pgbench 得出完全没有意义的数字。下面给出一些有助于获得有用结果的准则。

首先,绝不要相信任何只运行了几秒钟的测试。使用 -t 或 -T 选项让测试至少持续几分钟,以便平滑掉噪声。在某些情况下,可能需要数小时才能得到可复现的结果。一个好做法是把同一测试运行几次,看看结果是否可复现。

对于默认的类 TPC-B 测试场景,初始化比例因子(-s)应至少与计划测试的最大客户端数(-c)一样大;否则,测到的主要将是更新争用。pgbench_branches 表中只有 -s 行,而每个事务都要更新其中一行,因此 -c 超过 -s 时,必然会有大量事务阻塞等待其他事务。

默认测试场景还会对表初始化后的时间长短非常敏感:表中死元组和无效空间的累积会改变结果。要理解这些结果,必须跟踪更新总数以及何时发生清理。如果启用了自动清理,它可能会给测得的性能带来不可预测的变化。

pgbench 的一个局限是:在尝试测试大量客户端会话时,它自己也可能成为瓶颈。可以通过在与数据库服务器不同的机器上运行 pgbench 来缓解这一点,不过网络延迟必须足够低。甚至可以在多台客户端机器上同时运行多个 pgbench 实例,对同一台数据库服务器施压。

安全性

如果不受信任的用户能够访问尚未采用模式的安全使用方式的数据库,就不要在该数据库中运行 pgbench。pgbench 使用非限定名称,并且不会更改搜索路径。

报告文档问题

阅读 上游文档. 通过 PostgreSQL 文档反馈表单.