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 的事务测试需要预先设置特定的表。pgbench 应该使用 -i(初始化)选项来调用,以创建并填充这些表。(当您测试自定义脚本时,不需要此步骤,而是需要执行测试所需的任何设置。)初始化如下所示
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 (填充因子) 选项。
完成必要的设置后,可以使用不包含 -i 的命令来运行基准测试,即
pgbench [options]dbname
在几乎所有情况下,您都需要一些选项来进行有用的测试。最重要的选项是 -c(客户端数)、-t(事务数)、-T(时间限制)和 -f(指定自定义脚本文件)。有关完整列表,请参见下文。
以下内容分为三个小节。不同的选项在数据库初始化期间和运行基准测试期间使用,但某些选项在这两种情况下都很有用。
pgbench 接受以下命令行初始化参数
[-d] dbname[--dbname=]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 大量使用客户端/服务器带宽。pgbench 将 PostgreSQL 14 或更高版本的 FREEZE 选项一起使用,以加快后续的 VACUUM,除非启用了分区,否则在 pgbench_accounts 表上不使用。使用 g 会使日志在为所有表生成数据时每 100,000 行打印一条消息。
使用 G(服务器端数据生成),只有小查询从 pgbench 客户端发送,然后数据实际上在服务器中生成。此变体不需要大量带宽,但服务器将执行更多工作。使用 G 会使日志在生成数据时不打印任何进度消息。
默认初始化行为使用客户端数据生成(等效于 g)。
v (Vacuum) #在标准表上调用 VACUUM。
p (创建主键) #在标准表上创建主键索引。
f (创建外键) #在标准表之间创建外键约束。(请注意,默认情况下不执行此步骤。)
-F fillfactor--fillfactor=fillfactor #使用给定的填充因子创建 pgbench_accounts、pgbench_tellers 和 pgbench_branches 表。默认为 100。
-n--no-vacuum #在初始化期间不执行任何清理。(此选项会抑制 v 初始化步骤,即使它在 -I 中指定。)
-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 #创建一个分区 pgbench_accounts 表,其中包含 NUM 个分区,这些分区的规模大致相等,用于容纳缩放后的帐户数量。默认值为 0,表示不进行分区。
--tablespace=tablespace #在指定的表空间中创建表,而不是在默认表空间中创建。
--unlogged-tables #将所有表创建为未记录的表,而不是永久表。
pgbench 接受以下命令行基准测试参数:
-b scriptname[@weight]--builtin=scriptname[@weight] #将指定的内置脚本添加到要执行的脚本列表中。可用的内置脚本有:tpcb-like、simple-update 和 select-only。接受内置名称的明确前缀。使用特殊名称 list,显示内置脚本列表并立即退出。
可以选择在 @ 后写入整数权重,以调整选择此脚本相对于其他脚本的概率。默认权重为 1。有关详细信息,请参见下文。
-c clients--client=clients #模拟的客户端数量,即并发数据库会话的数量。默认值为 1。
-C--connect #为每个事务建立一个新连接,而不是每个客户端会话只建立一次连接。这对于测量连接开销很有用。
-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 毫秒的事务将被单独计数并报告为延迟。
当使用节流时(--rate=...),延迟超过计划时间 limit 毫秒的事务,因此没有希望满足延迟限制的事务根本不会发送到服务器。它们将被单独计数并报告为跳过。
当使用 --max-tries 选项时,由于序列化异常或死锁而失败的事务,如果其所有尝试的总时间大于 limit 毫秒,则不会重试。要仅限制尝试的时间,而不限制其次数,请使用 --max-tries=0。默认情况下,--max-tries 选项设置为 1,并且不会重试具有序列化/死锁错误的事务。有关重试此类事务的更多信息,请参阅故障和序列化/死锁重试。
-M querymode--protocol=querymode #用于将查询提交到服务器的协议
simple:使用简单查询协议。
extended:使用扩展查询协议。
prepared:使用带有预处理语句的扩展查询协议。
在 prepared 模式下,pgbench 从第二个查询迭代开始重用解析分析结果,因此 pgbench 的运行速度比其他模式更快。
默认值为简单查询协议。(有关更多信息,请参阅第 53 章。)
-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 选项一起使用。使用此选项,日志将包含每个间隔的摘要数据,如下所述。
--exit-on-abort #当任何客户端由于某些错误而中止时,立即退出。如果没有此选项,即使某个客户端中止,其他客户端也可以按照 -t 或 -T 选项的指定继续运行,并且在这种情况下 pgbench 将打印不完整的结果。
请注意,序列化失败或死锁失败不会中止客户端,因此不受此选项的影响。有关更多信息,请参阅失败和序列化/死锁重试。
--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 使用它来调度事务)。显式设置时,用于播种的值将显示在终端上。可以通过环境变量 PGBENCH_RANDOM_SEED 提供 seed 允许的任何值。为确保提供的种子影响所有可能的用途,请将此选项放在首位或使用环境变量。
显式设置种子可以精确地重现 pgbench 运行,就随机数而言。由于随机状态是按线程管理的,这意味着对于相同的调用,如果每个线程有一个客户端,并且没有外部或数据依赖关系,则完全相同的 pgbench 运行。从统计的角度来看,完全重现运行是一个坏主意,因为它会隐藏性能变化或不适当地提高性能,例如,通过命中与先前运行相同的页面。但是,它也可能对调试有很大帮助,例如,重新运行导致错误的棘手案例。请谨慎使用。
--sampling-rate=rate #采样率,用于将数据写入日志时,以减少生成的日志量。如果给出此选项,则仅记录指定比例的事务。1.0 表示将记录所有事务,0.05 表示仅记录 5% 的事务。
请记住在处理日志文件时考虑采样率。例如,在计算 TPS 值时,您需要相应地乘以数字(例如,使用 0.01 采样率,您只能获得实际 TPS 的 1/100)。
--show-script=scriptname #在 stderr 上显示内置脚本 scriptname 的实际代码,并立即退出。
--verbose-errors #打印有关所有错误和失败(没有重试的错误)的消息,包括超过了哪个重试限制,以及序列化/死锁失败的超出程度。(请注意,在这种情况下,输出可能会显着增加。)。有关更多信息,请参阅失败和序列化/死锁重试。
成功运行将以状态 0 退出。退出状态 1 表示静态问题,例如无效的命令行选项或应该永远不会发生的内部错误。在启动基准测试时发生的早期错误,例如初始连接失败,也会以状态 1 退出。运行期间的错误,例如数据库错误或脚本中的问题,将导致退出状态 2。在后一种情况下,如果未指定 --exit-on-abort 选项,则 pgbench 将打印部分结果。
PGDATABASEPGHOSTPGPORTPGUSER #默认连接参数。
此实用程序,像大多数其他 PostgreSQL 实用程序一样,使用 libpq 支持的环境变量(请参阅第 32.15 节)。
环境变量 PG_COLOR 指定是否在诊断消息中使用颜色。可能的值为 always、auto 和 never。
pgbench 执行从指定列表中随机选择的测试脚本。脚本可能包括使用 -b 指定的内置脚本和使用 -f 指定的用户提供的脚本。每个脚本都可以给出一个在 @ 之后指定的相对权重,以便改变其选择概率。默认权重为 1。权重为 0 的脚本将被忽略。
默认的内置事务脚本(也使用 -b tpcb-like 调用)在随机选择的 aid、tid、bid 和 delta 上,每个事务发出七个命令。该场景的灵感来自 TPC-B 基准测试,但实际上不是 TPC-B,因此得名。
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;
如果你选择 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 命令行选项预设的任何变量之外,还有一些变量是自动预设的,在表 298中列出。使用 -D 为这些变量指定的值优先于自动预设的值。设置后,可以通过写入 :变量名 将变量的值插入到 SQL 命令中。当运行多个客户端会话时,每个会话都有自己的一组变量。pgbench 支持在一个语句中使用多达 255 个变量。
表 298. pgbench 自动变量
| 变量 | 描述 |
|---|---|
client_id |
标识客户端会话的唯一编号(从零开始) |
default_seed |
默认情况下在哈希和伪随机置换函数中使用的种子 |
random_seed |
随机生成器种子(除非被 -D 覆盖) |
scale |
当前比例因子 |
脚本文件元命令以反斜杠 (\) 开头,通常延伸到行尾,但可以通过写入反斜杠-回车符继续到其他行。元命令的参数以空格分隔。支持以下元命令
\gset [前缀] \aset [前缀] #这些命令可用于结束 SQL 查询,取代结束分号 (;)。
当使用 \gset 命令时,期望前面的 SQL 查询返回一行,其列存储到以列名命名的变量中,如果提供了 前缀,则使用 前缀 作为前缀。
当使用 \aset 命令时,所有组合的 SQL 查询(以 \; 分隔)的列都存储到以列名命名的变量中,如果提供了 前缀,则使用 前缀 作为前缀。如果查询未返回任何行,则不会进行赋值,并且可以测试变量是否存在以检测这种情况。如果查询返回多行,则保留最后一个值。
\gset 和 \aset 不能在管道模式下使用,因为命令需要它们时查询结果尚不可用。
以下示例将第一个查询的最终帐户余额放入变量 abalance 中,并使用第三个查询中的整数填充变量 p_two 和 p_three。第二个查询的结果将被丢弃。最后两个组合查询的结果存储在变量 four 和 five 中。
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid RETURNING abalance \gset -- compound of two queries SELECT 1 \; SELECT 2 AS two, 3 AS three \gset p_ SELECT 4 AS four \; SELECT 5 AS five \aset
\if 表达式\elif 表达式\else\endif #这组命令实现可嵌套的条件块,类似于 psql 的 \if 表达式。条件表达式与 \set 的条件表达式相同,非零值被解释为 true。
\set 变量名 表达式 #将变量 变量名 设置为从 表达式 计算的值。表达式可以包含 NULL 常量、布尔常量 TRUE 和 FALSE、整数常量(例如 5432)、双精度常量(例如 3.14159)、对变量的引用 :变量名、具有常用 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 数字 [ us | ms | s ] #使脚本执行休眠指定的时间,单位为微秒 (us)、毫秒 (ms) 或秒 (s)。如果省略单位,则默认为秒。数字 可以是整数常量,也可以是引用具有整数值的变量的 :变量名。
示例
\sleep 10 ms
\setshell 变量名 命令 [ 参数 ... ] #将变量 变量名 设置为带有给定 参数 的 shell 命令 命令 的结果。该命令必须通过其标准输出返回一个整数值。
命令 和每个 参数 可以是文本常量,也可以是对变量的 :变量名 引用。如果要使用以冒号开头的 参数,请在 参数 的开头写入额外的冒号。
示例
\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
\shell 命令 [ 参数 ... ] #与 \setshell 相同,但命令的结果将被丢弃。
示例
\shell command literal_argument :variable ::literal_starting_with_colon
\startpipeline\syncpipeline\endpipeline #这组命令实现了 SQL 语句的流水线处理。管道必须以 \startpipeline 开头,并以 \endpipeline 结尾。在它们之间可以有任意数量的 \syncpipeline 命令,这些命令发送一个 同步消息,而不会结束正在进行的管道并刷新发送缓冲区。在管道模式下,语句被发送到服务器,而无需等待先前语句的结果。有关更多详细信息,请参阅 第 32.5 节。管道模式需要使用扩展查询协议。
表 299 中列出的算术、按位、比较和逻辑运算符内置于 pgbench 中,并且可以用于出现在 \set 中的表达式中。运算符按优先级递增的顺序排列。除非另有说明,否则采用两个数值输入的运算符,如果任一输入为双精度类型,则会生成双精度值,否则会生成整数结果。
表 299. pgbench 运算符
|
运算符 描述 示例 |
|---|
|
逻辑或
|
|
逻辑与
|
|
逻辑非
|
|
布尔值测试
|
|
空值测试
|
|
等于
|
|
不等于
|
|
不等于
|
|
小于
|
|
小于或等于
|
|
大于
|
|
大于或等于
|
|
按位或
|
|
按位异或
|
|
按位与
|
|
按位非
|
|
按位左移
|
|
按位右移
|
|
加法
|
|
减法
|
|
乘法
|
|
除法(如果两个输入都是整数,则结果向零截断)
|
|
模(余数)
|
|
取反
|
表 300 中列出的函数内置于 pgbench 中,可以在 \set 中出现的表达式中使用。
表 300. pgbench 函数
|
函数 描述 示例 |
|---|
|
绝对值
|
|
将参数打印到 stderr,并返回该参数。
|
|
转换为 double 类型。
|
|
指数(
|
|
选择参数中的最大值。
|
|
这是
|
|
计算 FNV-1a 哈希。
|
|
计算 MurmurHash2 哈希。
|
|
转换为整数。
|
|
选择参数中的最小值。
|
|
自然对数
|
|
模(余数)
|
|
|
|
π 的近似值
|
|
|
|
计算
|
|
计算
|
|
计算
|
|
计算
|
|
平方根
|
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,访问分布就越平坦(越均匀)。对该分布的一个粗略近似是,在范围内,最接近 min 的最频繁的 1% 的值,会被抽取 parameter% 的时间。parameter 值必须严格为正。
对于高斯分布,该区间被映射到一个标准的正态分布(经典钟形高斯曲线),在左侧的 -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)**parameterrandom_zipfian(1, ..., 2.5) 生成值 1 的频率大约比 2 高 (2/1)**2.5 = 5.66 倍,而 2 的生成频率又比 3 高 (3/2)**2.5 = 2.76 倍,依此类推。
pgbench 的实现基于 Luc Devroye 的 “Non-Uniform Random Variate Generation”,第 550-551 页,Springer 1986。由于该算法的局限性,parameter 值被限制在 [1.001, 1000] 范围内。
在设计选择行不均匀的基准时,请注意选择的行可能与其他数据相关,例如来自序列的 ID 或物理行排序,这可能会扭曲性能测量。
为了避免这种情况,您可能希望使用 permute 函数,或采取其他类似效果的额外步骤,来洗牌所选的行并消除此类相关性。
哈希函数 hash、hash_murmur2 和 hash_fnv1a 接受一个输入值和一个可选的种子参数。如果未提供种子,则使用 :default_seed 的值,除非由命令行 -D 选项设置,否则该值将随机初始化。
permute 接受一个输入值、一个大小和一个可选的种子参数。它生成 [0, size) 范围内整数的伪随机排列,并返回输入值在排列值中的索引。所选的排列由种子参数化,如果未指定,则默认为 :default_seed。与哈希函数不同,permute 确保输出值中没有冲突或空洞。区间之外的输入值被解释为模大小。如果大小不是正数,则该函数会引发错误。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.nnnprefix 默认为 pgbench_log,而 nnn 是 pgbench 进程的 PID。可以使用 --log-prefix 选项更改前缀。如果 -j 选项为 2 或更高,这样会有多个工作线程,则每个线程将有自己的日志文件。第一个工作线程的日志文件名称与标准单工作线程情况下的名称相同。其他工作线程的附加日志文件将命名为 ,其中 prefix.nnn.mmmmmm 是从 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 毫秒) 超过了 5 毫秒的限制。接下来的两个事务被跳过,因为它们在启动之前就已经迟到了。
以下示例显示了包含失败和重试的日志文件的片段,其中最大重试次数设置为 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 为每个语句收集以下统计信息:
latency — 每个语句的已用事务时间。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 错误(应该永远不会发生...)。
线程管理客户端时的错误(例如,客户端无法启动与数据库服务器的连接/将客户端连接到数据库服务器的套接字已失效)。在这种情况下,此线程的所有客户端都会停止,而其他线程继续工作。但是,如果指定了 --exit-on-abort,则所有线程都会在此情况下立即停止。
直接客户端错误。如果发生内部 pgbench 错误(应该永远不会发生...),或者指定了 --exit-on-abort,它们会导致 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 表指定 表访问方法。环境变量 PGOPTIONS 指定通过命令行传递给 PostgreSQL 的数据库配置选项(请参阅 第 19.1.4 节)。例如,可以使用以下方法指定 pgbench 创建的表假设的默认表访问方法,称为 wuzza:
PGOPTIONS='-c default_table_access_method=wuzza'
使用 pgbench 非常容易产生完全没有意义的数字。以下是一些指导原则,可帮助您获得有用的结果。
首先,永远不要相信只运行几秒钟的任何测试。使用 -t 或 -T 选项使运行持续至少几分钟,以消除噪声。在某些情况下,您可能需要数小时才能获得可重现的数字。尝试运行几次测试是一个好主意,以找出您的数字是否可重现。
对于默认的类 TPC-B 测试场景,初始化比例因子 (-s) 应至少与您打算测试的最大客户端数量 (-c) 一样大;否则您将主要测量更新争用。pgbench_branches 表中只有 -s 行,并且每个事务都想更新其中的一个,因此超过 -s 的 -c 值无疑会导致大量事务被阻止等待其他事务。
默认测试场景也对表初始化以来的时间长短非常敏感:表中死行和死空间的累积会改变结果。要了解结果,您必须跟踪更新总数以及何时发生清理。如果启用了自动清理,则可能会导致测得的性能出现不可预测的变化。
pgbench 的一个限制是,当尝试测试大量客户端会话时,它本身可能会成为瓶颈。这可以通过在与数据库服务器不同的计算机上运行 pgbench 来缓解,尽管低网络延迟至关重要。甚至可能有用的是在多个客户端计算机上针对同一数据库服务器同时运行多个 pgbench 实例。
如果不可信用户可以访问一个没有采用安全模式使用模式的数据库,请不要在该数据库中运行 pgbench。pgbench 使用非限定名称,并且不操作搜索路径。
如果您发现文档中任何不正确、与您对特定功能的体验不符或需要进一步澄清的地方,请使用此表格报告文档问题。