F.26. pgbench #
pgbench是一个用于对PostgreSQL执行基准测试的简单程序。它会反复执行同一组 SQL 命令,也可在多个并发数据库会话中运行,然后计算平均事务速率(每秒事务数)。默认情况下,pgbench测试的是一个大体上基于 TPC-B 的场景,每个事务包含五条SELECT、UPDATE和INSERT命令。不过,通过编写自己的事务脚本文件,也很容易测试其他场景。
下面是 pgbench 的典型输出:
transaction type: TPC-B (sort of) scaling factor: 10 query mode: simple number of clients: 10 number of threads: 1 number of transactions per client: 1000 number of transactions actually processed: 10000/10000 tps = 85.184871 (including connections establishing) tps = 85.296346 (excluding connections establishing)
前六行报告了一些最重要的参数设置。下一行报告已完成的事务数和预期的事务数(后者就是客户端数与每个客户端的事务数的乘积);除非运行在完成前失败,否则这两个数应该相等。(在 -T 模式下,只打印实际的事务数。)最后两行报告每秒事务数,分别计入和不计入启动数据库会话的时间。
F.26.1. 概述
默认的类 TPC-B 事务测试要求预先建立特定的表。应使用
-i(初始化)选项调用
pgbench 来创建并填充这些表。(测试自定义脚本时不需要这一步,但需要自行完成测试所需的准备工作。)初始化命令如下:
pgbench -i [other-options]dbname
其中
dbname 是要在其中进行测试的已创建数据库的名称。(可能还需要
-h、-p 和/或
-U 选项来指定如何连接数据库服务器。)
小心
pgbench -i会创建四个表pgbench_accounts、pgbench_branches、pgbench_history和pgbench_tellers,并销毁任何已存在的同名表。如果数据库中已经存在这些名称的表,请务必改用其他数据库!
At the default “scale factor” of 1, the tables initially contain this many rows:
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(指定自定义脚本文件)。完整列表见下文。
第 F.26.2 节给出数据库初始化期间使用的选项,第 F.26.3 节给出运行基准测试期间使用的选项,而第 F.26.4 节给出两种情况下都有用的选项。
F.26.2. pgbench 初始化选项 #
pgbench 接受以下命令行初始化参数:
-i进入初始化模式所必需。
-Ffillfactor以给定的 fillfactor 创建
pgbench_accounts、pgbench_tellers和pgbench_branches表。默认值为 100。-sscale_factor将生成的行数乘以缩放因子。例如,
-s 100会在pgbench_accounts表中创建 10,000,000 行。默认值为 1。
F.26.3. pgbench Benchmarking Options #
pgbench 接受以下命令行基准测试参数:
-cclients模拟的客户端数量,也就是并发数据库会话的数量。默认值为 1。
-C为每个事务建立一个新的连接,而不是每个客户端会话只建立一次。这有助于测量连接开销。
-d打印调试输出。
-Dvarname=value为自定义脚本定义一个变量(见下文)。允许使用多个
-D选项。-ffilename从
filename读取事务脚本。详见下文。-N、-S和-f互斥。-jthreadspgbench 内的工作线程数。在多 CPU 机器上使用多个线程会有帮助。客户端数必须时线程数的整数倍,因为每个线程都会分到相同数量的客户端会话来管理。默认值为 1。
-l将每个事务所耗时间写入日志文件。详见下文。
-Mquerymode用于向服务器提交查询的协议:
simple:使用简单查询协议。extended:使用扩展查询协议。prepared:使用带预备语句的扩展查询协议。
The default is simple query protocol. (See 第 46 章 for more information.)
-n运行测试前不执行清理。如果你运行的自定义测试场景不包含标准表
pgbench_accounts、pgbench_branches、pgbench_history和pgbench_tellers,则此选项是必需的。-N不更新
pgbench_tellers和pgbench_branches。这会避免在这些表上发生更新争用,但也会让该测试场景更不像 TPC-B。-r在基准测试完成后,报告每条命令的平均语句延迟(从客户端视角看到的执行时间)。详情见下文。
-sscale_factor在 pgbench 的输出中报告指定的缩放因子。对于内置测试,这不是必需的;正确的缩放因子会通过统计
pgbench_branches表中的行数检测出来。但是,在测试自定义基准(-f选项)时,除非使用此选项,否则缩放因子将报告为 1。-S执行只读 SELECT 事务,而不是类 TPC-B 测试。
-ttransactions每个客户端运行的事务数。默认值为 10。
-Tseconds让测试运行这么多秒,而不是每个客户端固定数量的事务。
-t和-T互斥。-v运行测试前清理全部四个标准表。若既不指定
-n也不指定-v,pgbench 将清理pgbench_tellers和pgbench_branches表,并截断pgbench_history。
F.26.4. pgbench Common Options #
pgbench 接受以下通用命令行参数:
-hhostname数据库服务器的主机名。
-pport数据库服务器的端口号。
-Ulogin用于连接的用户名
F.26.5. 在 pgbench 中实际执行的“事务”是什么?
默认的事务脚本在每个事务中发出七条命令:
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;
如果指定-N,事务中将不包含第 4 步和第 5 步。如果指定-S,则只执行SELECT。
F.26.6. 自定义脚本
pgbench 支持运行自定义基准场景,方法是用从文件读取的事务脚本(-f 选项)替换(前文所述的)默认事务脚本。在这种情况下,一次“事务”算作脚本文件的一次执行。甚至可以指定多个脚本(多个
-f 选项),此时每次客户端会话开始新事务时,都会随机选择其中一个脚本。
脚本文件的格式是每行一条 SQL 命令;不支持跨多行的 SQL 命令。空行以及以--开头的行会被忽略。脚本文件的行还可以是“元命令”,它们由pgbench自身解释,详见下文。
脚本文件有一个简单的变量替换设施。变量可以通过命令行
-D 选项(前文已说明)或下文说明的元命令来设置。除了由
-D 命令行选项预设的变量之外,变量
scale 会被预设为当前缩放因子。变量一经设置,就可以通过写成
:variablename 把它的值插入 SQL 命令中。运行多个客户端会话时,每个会话都有自己的一组变量。
脚本文件中的元命令以反斜线(\)开头。元命令的参数以空白分隔。支持的元命令如下:
-
\setvarnameoperand1[operatoroperand2] 将变量
varname设置为一个计算出的整数值。每个operand要么是一个整数常量,要么是一个对具有整数值的变量的:variablename引用。operator可以是+、-、*或/。示例:
\set ntellers 10 * :scale
-
\setrandomvarnameminmax 将变量
varname设置为介于min和max之间(含边界)的一个随机整数值。每个界限既可以是整数常量,也可以是指向某个整数值变量的:variablename引用。示例:
\setrandom aid 1 :naccounts
-
\sleepnumber[ us | ms | s ] 让脚本执行休眠指定时长,单位可以是微秒(
us)、毫秒(ms)或秒(s)。如果省略单位,则默认为秒。number可以是整数常量,也可以是引用了整数值变量的:variablename。示例:
\sleep 10 ms
-
\setshellvarnamecommand[argument... ] 将变量
varname设置为 shell 命令command的结果。该命令必须通过标准输出返回一个整数值。argument可以是文本常量,也可以是指向任意类型变量的:variablename引用。如果要使用以冒号开头的argument,需要在argument开头再添加一个冒号。示例:
\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
-
\shellcommand[argument... ] 与
\setshell相同,但结果会被忽略。示例:
\shell command literal_argument :variable ::literal_starting_with_colon
作为一个示例,内置的类 TPC-B 事务的全部定义是:
\set nbranches :scale \set ntellers 10 * :scale \set naccounts 100000 * :scale \setrandom aid 1 :naccounts \setrandom bid 1 :nbranches \setrandom tid 1 :ntellers \setrandom delta -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;
该脚本允许事务的每次迭代都引用不同的随机选中行。(这个示例也说明了为什么每个客户端会话都必须拥有自己的变量 — 否则它们就无法彼此独立地访问不同的行。)
F.26.7. 逐事务日志记录
使用
-l 选项时,pgbench
会把每个事务所耗时间写入日志文件。日志文件名为
pgbench_log.,其中
nnnnnn 是 pgbench 进程的 PID。如果
-j 选项为 2 或更高(创建多个工作线程),每个线程都会有自己的日志文件。第一个工作线程的日志文件名与标准单工作线程情形相同。其余工作线程的额外日志文件名为
pgbench_log.,其中
nnn.mmmmmm 是从 1 开始的各工作线程顺序编号。
日志格式如下:
client_idtransaction_notimefile_notime_epochtime_us
其中,time 是事务经过的总时间,单位为微秒,file_no 标识使用的脚本文件(在通过 -f 指定多个脚本时很有用),而 time_epoch/time_us 分别是 Unix 纪元格式的时间戳和以微秒计的偏移量(适合用来生成带小数秒的 ISO 8601 时间戳),表示事务完成的时间。
示例输出如下:
0 199 2241 0 1175850568 995598 0 200 2465 0 1175850568 998079 0 201 2513 0 1175850569 608 0 202 2038 0 1175850569 2663
F.26.8. 逐语句延迟
使用-r选项时,pgbench会收集每个客户端执行的每条语句所经过的事务时间。基准测试完成后,它会报告这些值的平均值,称为每条语句的延迟。
对于默认脚本,输出与下面类似:
starting vacuum...end.
transaction type: TPC-B (sort of)
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
tps = 618.764555 (including connections establishing)
tps = 622.977698 (excluding connections establishing)
statement latencies in milliseconds:
0.004386 \set nbranches 1 * :scale
0.001343 \set ntellers 10 * :scale
0.001212 \set naccounts 100000 * :scale
0.001310 \setrandom aid 1 :naccounts
0.001073 \setrandom bid 1 :nbranches
0.001005 \setrandom tid 1 :ntellers
0.001078 \setrandom delta -5000 5000
0.326152 BEGIN;
0.603376 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
0.454643 SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
5.528491 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
7.335435 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
0.371851 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
1.212976 END;
如果指定了多个脚本文件,则会分别为每个脚本文件报告平均值。
注意,为逐语句延迟计算收集额外的计时信息会带来一定开销。这会拖慢平均执行速度,并降低计算出的 TPS。减速幅度在很大程度上取决于平台和硬件。比较启用和未启用延迟报告时的平均 TPS 值,是判断这一计时开销是否显著的好方法。
F.26.9. 良好实践
很容易用pgbench得出完全没有意义的数字。下面给出一些有助于获得有用结果的准则。
首先,绝不要相信任何只运行几秒钟的测试。使用
-t 或
-T 选项让运行至少持续几分钟,以平均掉噪声。在某些情况下,可能需要数小时才能获得可重现的数字。最好把测试多运行几次,看看你的数字是否可重现。
对于默认的类 TPC-B 测试场景,初始化缩放因子(-s)至少应与你打算测试的最大客户端数(-c)一样大;否则你测到的将主要是更新争用。pgbench_branches 表中只有
-s 行,而每个事务都要更新其中一行,因此
-c 值超过
-s 无疑会导致大量事务阻塞等待其他事务。
默认测试场景还会对表初始化后的时间长短非常敏感:表中死元组和无效空间的累积会改变结果。要理解这些结果,必须跟踪更新总数以及何时发生清理。如果启用了自动清理,它可能会给测得的性能带来不可预测的变化。
pgbench的一个局限是:在尝试测试大量客户端会话时,它自己也可能成为瓶颈。可以通过在与数据库服务器不同的机器上运行pgbench来缓解这一点,不过网络延迟必须足够低。甚至可以在多台客户端机器上同时运行多个pgbench实例,对同一台数据库服务器施压。