pg_stat_statements 模块提供跟踪服务器执行的所有 SQL 语句的规划与执行统计信息的手段。
该模块必须通过在 postgresql.conf 的 shared_preload_libraries 中加入 pg_stat_statements 来加载,因为它需要额外的共享内存。这意味着添加或移除该模块都需要重启服务器。此外,必须启用查询标识符计算模块才能生效;如果 compute_query_id 设为 auto 或 on,或加载了任何计算查询标识符的第三方模块,这会自动完成。
当 pg_stat_statements 处于活动状态时,它会跟踪服务器所有数据库的统计信息。为了访问与操作这些统计信息,该模块提供视图 pg_stat_statements 与 pg_stat_statements_info,以及实用函数 pg_stat_statements_reset 与 pg_stat_statements。它们在全局不可用,但可以通过 CREATE EXTENSION pg_stat_statements 为特定数据库启用。
pg_stat_statements 视图 #
该模块收集的统计信息通过名为 pg_stat_statements 的视图提供。该视图对数据库 ID、用户 ID、查询 ID 以及是否为顶层语句的每种不同组合各占一行(最多达到该模块能跟踪的最大不同语句数)。视图的列见
Table F.22。
Table F.22. pg_stat_statements 列
列类型 描述 |
|---|
执行该语句的用户的 OID |
执行该语句的数据库的 OID |
如果该查询作为顶层语句执行则为真(当 |
用于识别相同规范化查询的哈希码。 |
代表性语句的文本 |
该语句被规划的次数(如果启用了 |
规划该语句所用的总时间(毫秒)(如果启用了 |
规划该语句所用的最小时间(毫秒)。如果 |
规划该语句所用的最大时间(毫秒)。如果 |
规划该语句所用的平均时间(毫秒)(如果启用了 |
规划该语句所用时间的总体标准差(毫秒)(如果启用了 |
该语句被执行的次数 |
执行该语句所用的总时间(毫秒) |
执行该语句所用的最小时间(毫秒);在该语句于 |
执行该语句所用的最大时间(毫秒);在该语句于 |
执行该语句所用的平均时间(毫秒) |
执行该语句所用时间的总体标准差(毫秒) |
该语句检索或影响的总行数 |
该语句的共享块缓存命中总数 |
该语句读取的共享块总数 |
该语句弄脏的共享块总数 |
该语句写入的共享块总数 |
该语句的本地块缓存命中总数 |
该语句读取的本地块总数 |
该语句弄脏的本地块总数 |
该语句写入的本地块总数 |
该语句读取的临时块总数 |
该语句写入的临时块总数 |
该语句读取共享块所用的总时间(毫秒)(如果启用了 track_io_timing,否则为零) |
该语句写入共享块所用的总时间(毫秒)(如果启用了 track_io_timing,否则为零) |
该语句读取本地块所用的总时间(毫秒)(如果启用了 track_io_timing,否则为零) |
该语句写入本地块所用的总时间(毫秒)(如果启用了 track_io_timing,否则为零) |
该语句读取临时文件块所用的总时间(毫秒)(如果启用了 track_io_timing,否则为零) |
该语句写入临时文件块所用的总时间(毫秒)(如果启用了 track_io_timing,否则为零) |
该语句生成的 WAL 记录总数 |
该语句生成的 WAL 全页镜像总数 |
该语句生成的 WAL 总量(字节) |
WAL 缓冲区变满的次数 |
该语句进行 JIT 编译的函数总数 |
该语句生成 JIT 代码所用的总时间(毫秒) |
函数被内联的次数 |
该语句内联函数所用的总时间(毫秒) |
该语句被优化的次数 |
该语句优化所用的总时间(毫秒) |
代码被发射的次数 |
该语句发射代码所用的总时间(毫秒) |
该语句进行 JIT 编译的元组解构函数总数 |
该语句 JIT 编译元组解构函数所用的总时间(毫秒) |
计划启动的并行工作进程数量 |
实际启动的并行工作进程数量 |
该语句开始收集统计信息的时间 |
该语句开始收集最小/最大统计信息的时间(字段 |
出于安全考虑,只有超级用户和拥有 pg_read_all_stats 角色特权的角色可以查看其他用户执行的查询的 SQL 文本与 queryid。不过,只要视图已安装在其数据库中,其他用户也可以查看统计信息。
可规划查询(即 SELECT、INSERT、UPDATE、DELETE 与 MERGE)与实用命令只要根据内部哈希计算具有相同的查询结构,就会被合并为单个 pg_stat_statements 条目。通常,如果两个查询在语义上等价、只有查询中出现的字面量常量的值不同,就会被视为同一个查询。
以下关于常量替换与 queryid 的细节只在启用 compute_query_id 时适用。如果你改用外部模块计算 queryid,细节请参阅其文档。
当某个常量的值为了与其他查询匹配而被忽略时,该常量在 pg_stat_statements 显示中会被替换为参数符号,例如 $1。查询文本的其余部分是第一个与该 pg_stat_statements 条目关联的特定 queryid 哈希值的查询的文本。
可应用规范化的查询在 pg_stat_statements 中可能被观察到带有常量值,尤其在条目释放率高的情况下。为降低发生这种情况的可能性,可以考虑增大 pg_stat_statements.max。下文 Section F.32.2 讨论的 pg_stat_statements_info 视图提供关于条目释放的统计信息。
在某些情况下,文本看起来不同的查询可能被合并为单个 pg_stat_statements 条目;如上所述,这对语义等价的查询是预期行为。此外,如果查询之间唯一的区别是常量列表中的元素数量,该列表会被压缩为单个元素,但会带上被注释掉的列表指示符显示:
=# SELECT pg_stat_statements_reset(); =# SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5, 6, 7); =# SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5, 6, 7, 8); =# SELECT query, calls FROM pg_stat_statements WHERE query LIKE 'SELECT%'; -[ RECORD 1 ]------------------------------ query | SELECT * FROM test WHERE a IN ($1 /*, ... */) calls | 2
除这些情况之外,哈希冲突也有很小的概率导致不相关的查询被合并为一个条目。(不过,属于不同用户或数据库的查询不会发生这种情况。)
由于 queryid 哈希值是对查询解析后分析表示计算的,反过来也可能成立:文本完全相同的查询可能显示为单独的条目,例如由于 search_path 设置不同等因素导致它们具有不同含义。
pg_stat_statements 的使用者可能希望使用 queryid(也许结合 dbid 与 userid)作为比查询文本更稳定、更可靠的每个条目的标识符。不过,重要的是要理解,围绕 queryid 哈希值的稳定性只有有限的保证。由于标识符派生自解析后分析树,它的值(除其他因素外)是该表示中出现的内部对象标识符的函数。这带来一些违反直觉的影响。例如,如果两个表面上完全相同的查询引用了某个在两次执行之间被删除又重建的函数,pg_stat_statements 会把它们视为不同。反过来,如果某个表在查询的两次执行之间被删除又重建,两个表面上相同的查询可能被视为相同。但如果表的别名不同,即使其他部分相似,这些查询也会被视为不同。哈希过程还对机器架构与平台的其他方面差异敏感。此外,不能假定 queryid 在 PostgreSQL 大版本之间保持稳定。
参与基于物理 WAL 回放的复制的两个服务器,对同一查询的 queryid 值可以预期是相同的。不过,逻辑复制方案并不承诺在所有相关细节上保持副本完全一致,因此 queryid 不是用于跨一组逻辑副本累积成本的有用标识符。如有疑问,建议进行直接测试。
一般而言,只要实例运行在相同的机器架构上且目录元数据细节匹配,就可以假定 queryid 值在 PostgreSQL 的次要版本发布之间保持稳定。只有在万不得已时才会在次要版本之间破坏兼容性。
用于替换代表性查询文本中常量的参数符号,从原始查询文本中最高 $n 参数的下一个编号开始;如果没有则从 $1 开始。值得注意的是,在某些情况下可能存在影响该编号的隐藏参数符号。例如,PL/pgSQL 使用隐藏参数符号把函数局部变量的值插入查询,因此类似 SELECT i + 1 INTO j 的 PL/pgSQL 语句的代表性文本会是 SELECT i + $2。
代表性查询文本保存在外部磁盘文件中,不占用共享内存。因此,即使非常长的查询文本也能成功存储。不过,如果积累了许多长查询文本,外部文件可能增长到难以管理的大小。作为发生这种情况时的恢复手段,pg_stat_statements 可能选择丢弃查询文本,此后 pg_stat_statements 视图中所有现有条目的 query 字段将显示为空,但与每个 queryid 关联的统计信息会被保留。如果发生这种情况,考虑减小 pg_stat_statements.max 以防再次发生。
plans 与 calls 并不总是预期相等,因为规划与执行统计在各自结束阶段更新,并且只针对成功的操作。例如,如果一条语句规划成功但在执行阶段失败,只有它的规划统计会被更新。如果因使用了缓存计划而跳过规划,则只有它的执行统计会被更新。
pg_stat_statements_info 视图 #
pg_stat_statements 模块自身的统计信息通过名为 pg_stat_statements_info 的视图跟踪并提供。该视图只包含单行。视图的列见 Table F.23。
Table F.23. pg_stat_statements_info 列
列类型 描述 |
|---|
由于观察到的不同语句数量超过 |
|
pg_stat_statements_reset(userid Oid, dbid Oid, queryid
bigint, minmax_only boolean) returns timestamp with time zone
pg_stat_statements_reset 丢弃 pg_stat_statements 迄今为止收集的、与指定的 userid、dbid 与 queryid 相对应的统计信息。如果任何参数未指定,则对每个参数使用默认值 0(无效值),与其它参数匹配的统计信息将被重置。如果未指定任何参数,或所有指定的参数都是 0(无效值),则会丢弃所有统计信息。如果 pg_stat_statements 视图中的所有统计信息都被丢弃,pg_stat_statements_info 视图中的统计信息也会被重置。当 minmax_only 为 true 时,只重置最小与最大规划及执行时间的值(即 min_plan_time、max_plan_time、min_exec_time 与 max_exec_time 字段)。minmax_only 参数的默认值为 false。上次执行的最小/最大重置的时间显示在 pg_stat_statements 视图的 minmax_stats_since 字段中。该函数返回重置的时间。如果实际执行了相应的重置,该时间会保存到 pg_stat_statements_info 视图的 stats_reset 字段,或 pg_stat_statements 视图的 minmax_stats_since 字段。默认情况下,该函数只能由超级用户执行。可以通过 GRANT 将访问权限授予其他用户。
pg_stat_statements(showtext boolean) returns setof record
pg_stat_statements 视图是通过一个同样名为 pg_stat_statements 的函数定义的。客户端可以直接调用 pg_stat_statements 函数,并通过指定 showtext := false 省略查询文本(即与视图的 query 列对应的 OUT 参数将返回空值)。该功能旨在支持希望避免反复获取不定长度查询文本的开销的外部工具。这类工具可以自行缓存每个条目第一次观察到的查询文本(这正是 pg_stat_statements 本身所做的全部工作),然后只在需要时获取查询文本。由于服务器把查询文本存储在文件中,这种方式可以减少反复检查 pg_stat_statements 数据时的物理 I/O。
pg_stat_statements.max (integer)
pg_stat_statements.max 是该模块跟踪的最大语句数(即 pg_stat_statements 视图中的最大行数)。如果观察到的不同语句超过该数量,关于执行次数最少的语句的信息会被丢弃。这类信息被丢弃的次数可以在 pg_stat_statements_info 视图中看到。默认值为 5000。该参数只能在服务器启动时设置。
pg_stat_statements.track (enum)
pg_stat_statements.track 控制该模块统计哪些语句。指定 top 跟踪顶层语句(由客户端直接发出的语句),all 还跟踪嵌套语句(例如函数内调用的语句),或 none 禁用语句统计收集。默认值为 top。只有超级用户可以更改此设置。
pg_stat_statements.track_utility (boolean)
pg_stat_statements.track_utility 控制该模块是否跟踪实用命令。实用命令是指 SELECT、INSERT、UPDATE、DELETE 与 MERGE 之外的所有命令。默认值为 on。只有超级用户可以更改此设置。
pg_stat_statements.track_planning (boolean)
pg_stat_statements.track_planning 控制该模块是否跟踪规划操作及其时长。启用该参数可能带来明显的性能损失,尤其是当具有相同查询结构的语句由许多并发连接执行、而这些连接竞争更新少量 pg_stat_statements 条目时。默认值为 off。只有超级用户可以更改此设置。
pg_stat_statements.save (boolean)
pg_stat_statements.save 指定是否在服务器关闭期间保存语句统计信息。如果为 off,关闭时不保存统计信息,服务器启动时也不重新加载。默认值为 on。该参数只能在 postgresql.conf 文件或服务器命令行中设置。
该模块需要与 pg_stat_statements.max 成比例的额外共享内存。请注意,只要模块被加载就会消耗该内存,即使 pg_stat_statements.track 设为 none。
这些参数必须在 postgresql.conf 中设置。典型用法如下:
# postgresql.conf shared_preload_libraries = 'pg_stat_statements' compute_query_id = on pg_stat_statements.max = 10000 pg_stat_statements.track = all
bench=# SELECT pg_stat_statements_reset();
$ pgbench -i bench
$ pgbench -c10 -t300 bench
bench=# \x
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
calls | 3000
total_exec_time | 25565.855387
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls | 3000
total_exec_time | 20756.669379
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------------------------
query | copy pgbench_accounts from stdin
calls | 1
total_exec_time | 291.865911
rows | 100000
hit_percent | 100.0000000000000000
-[ RECORD 4 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls | 3000
total_exec_time | 271.232977
rows | 3000
hit_percent | 98.8454011741682975
-[ RECORD 5 ]---+--------------------------------------------------------------------
query | alter table pgbench_accounts add primary key (aid)
calls | 1
total_exec_time | 160.588563
rows | 0
hit_percent | 100.0000000000000000
bench=# SELECT pg_stat_statements_reset(0,0,s.queryid) FROM pg_stat_statements AS s
WHERE s.query = 'UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2';
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls | 3000
total_exec_time | 20756.669379
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------------------------
query | copy pgbench_accounts from stdin
calls | 1
total_exec_time | 291.865911
rows | 100000
hit_percent | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------------------------
query | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls | 3000
total_exec_time | 271.232977
rows | 3000
hit_percent | 98.8454011741682975
-[ RECORD 4 ]---+--------------------------------------------------------------------
query | alter table pgbench_accounts add primary key (aid)
calls | 1
total_exec_time | 160.588563
rows | 0
hit_percent | 100.0000000000000000
-[ RECORD 5 ]---+--------------------------------------------------------------------
query | vacuum analyze pgbench_accounts
calls | 1
total_exec_time | 136.448116
rows | 0
hit_percent | 99.9201915403032721
bench=# SELECT pg_stat_statements_reset(0,0,0);
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+-----------------------------------------------------------------------------
query | SELECT pg_stat_statements_reset(0,0,0)
calls | 1
total_exec_time | 0.189497
rows | 1
hit_percent |
-[ RECORD 2 ]---+-----------------------------------------------------------------------------
query | SELECT query, calls, total_exec_time, rows, $1 * shared_blks_hit / +
| nullif(shared_blks_hit + shared_blks_read, $2) AS hit_percent+
| FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT $3
calls | 0
total_exec_time | 0
rows | 0
hit_percent |
Takahiro Itagaki <itagaki.takahiro@oss.ntt.co.jp>。查询规范化由 Peter Geoghegan <peter@2ndquadrant.com> 添加。