10.2. 统计收集器 #
PostgreSQL 的统计收集器是一个支持收集和报告服务器活动信息的子系统。目前,收集器可以按磁盘块和单行两种方式统计对表和索引的访问。它还支持确定其他服务器进程当前正在执行的确切查询。
10.2.1. 统计信息收集配置 #
由于统计收集会给查询执行增加一些开销,系统可以配置为收集或不收集信息。这由通常在
postgresql.conf 中设置的配置变量控制(设置配置变量的详情见第 3.4 节)。
要让统计收集器能够启动,必须把变量
STATS_START_COLLECTOR 设为
true。这是默认且被推荐的设置,但如果你对统计信息不感兴趣并想榨干每一点开销,也可以把它关掉。(不过节省的开销可能很小。)注意这个选项在服务器运行期间不能更改。
变量 STATS_COMMAND_STRING、STATS_BLOCK_LEVEL 和
STATS_ROW_LEVEL 控制实际发送给收集器的信息量,从而决定运行时开销的大小。它们分别决定一个服务器进程是否把自己的当前命令串、磁盘块级访问统计和行级访问统计发送给收集器。通常这些变量在 postgresql.conf 中设置,从而作用于所有服务器进程,但可以用 SET 命令在单个服务器进程中开关它们。(为防止普通用户向管理员隐藏自己的活动,只有超级用户才被允许用 SET 更改这些变量。)
重要
由于变量 STATS_COMMAND_STRING、STATS_BLOCK_LEVEL 和
STATS_ROW_LEVEL 默认为 false,默认配置下实际上不收集任何统计信息!必须先打开其中一个或多个,统计显示函数才会给出有用的结果。
10.2.2. 查看收集到的统计信息 #
有若干预定义视图可用来显示统计收集的结果。另外,也可以用底层的统计函数构建自定义视图。
用统计信息监控当前活动时,重要的一点是要认识到这些信息不会即时更新。每个服务器进程在等待下一条客户端命令之前传输新的访问计数;因此仍在进行中的查询不会影响显示的总量。此外,收集器本身每最多 PGSTAT_STAT_INTERVAL(默认 500 毫秒)才发出一次新的总量。因此显示的总量落后于实际活动。
另一个要点是,当服务器进程被要求显示这些统计信息中的任何一种时,它会先取回收集器进程发出的最新总量,然后在当前事务结束之前继续使用这一快照用于所有统计视图和函数。因此,只要继续当前事务,统计信息看起来就不会变化。这是一个特性而不是缺陷,因为它允许你对统计信息执行多个查询并关联结果,而不必担心数字在你手下变化。但如果想让每个查询都看到新结果,请务必在任何事务块之外执行查询。
表 10.1. 标准统计视图
| 视图名 | 描述 |
|---|---|
pg_stat_activity | 每个服务器进程一行,显示进程 PID、数据库、用户和当前查询。当前查询列只有超级用户可用;对其他用户读取为 NULL。(注意,由于收集器的报告延迟,当前查询只对长时间运行的查询是最新的。) |
pg_stat_database | 每个数据库一行,显示活动后端数、该数据库中已提交和已回滚的事务总数、读取的磁盘块总数,以及缓冲区命中总数(即因在缓冲缓存中找到块而避免的块读请求数)。 |
pg_stat_all_tables | 当前数据库中的每个表:顺序扫描和索引扫描的总次数、每种扫描返回的元组总数,以及元组插入、更新和删除的总数。 |
pg_stat_sys_tables | 与 pg_stat_all_tables 相同,但只显示系统表。 |
pg_stat_user_tables | 与 pg_stat_all_tables 相同,但只显示用户表。 |
pg_stat_all_indexes | 当前数据库中的每个索引:使用该索引的索引扫描总次数、读取的索引元组数,以及成功取出的堆元组数。(当存在指向过期堆元组的索引条目时,后者可能更少。) |
pg_stat_sys_indexes | 与 pg_stat_all_indexes 相同,但只显示系统表上的索引。 |
pg_stat_user_indexes | 与 pg_stat_all_indexes 相同,但只显示用户表上的索引。 |
pg_statio_all_tables | 当前数据库中的每个表:从该表读取的磁盘块总数、缓冲区命中数、该表所有索引的磁盘块读取数和缓冲区命中数、该表辅助 TOAST 表(若有)的磁盘块读取数和缓冲区命中数,以及 TOAST 表索引的磁盘块读取数和缓冲区命中数。 |
pg_statio_sys_tables | 与 pg_statio_all_tables 相同,但只显示系统表。 |
pg_statio_user_tables | 与 pg_statio_all_tables 相同,但只显示用户表。 |
pg_statio_all_indexes | 当前数据库中的每个索引:该索引中的磁盘块读取数和缓冲区命中数。 |
pg_statio_sys_indexes | 与 pg_statio_all_indexes 相同,但只显示系统表上的索引。 |
pg_statio_user_indexes | 与 pg_statio_all_indexes 相同,但只显示用户表上的索引。 |
pg_statio_all_sequences | 当前数据库中的每个序列对象:该序列中的磁盘块读取数和缓冲区命中数。 |
pg_statio_sys_sequences | 与 pg_statio_all_sequences 相同,但只显示系统序列。(目前没有定义任何系统序列,因此该视图总是空的。) |
pg_statio_user_sequences | 与 pg_statio_all_sequences 相同,但只显示用户序列。 |
每个索引的统计信息对于判断哪些索引正在被使用以及它们有多有效尤其有用。
pg_statio_ 视图主要用来判断缓冲缓存的有效性。当实际磁盘读取数远小于缓冲区命中数时,缓存正在不调用内核的情况下满足大多数读请求。
通过编写使用与这些标准视图相同的底层统计访问函数的查询,可以建立查看统计信息的其他方式。每个数据库的访问函数接受一个数据库 OID 来标识要报告的数据库。每表和每索引的函数接受一个表或索引 OID(注意,用这些函数只能看到当前数据库中的表和索引)。每个后端的访问函数接受一个后端 ID 号,其范围从一到当前活动后端的数量。
表 10.2. 统计访问函数
| 函数 | 返回类型 | 描述 |
|---|---|---|
pg_stat_get_db_numbackends(oid) | integer | 数据库中的活动后端数 |
pg_stat_get_db_xact_commit(oid) | bigint | 数据库中已提交的事务数 |
pg_stat_get_db_xact_rollback(oid) | bigint | 数据库中已回滚的事务数 |
pg_stat_get_db_blocks_fetched(oid) | bigint | 数据库的磁盘块读取请求数 |
pg_stat_get_db_blocks_hit(oid) | bigint | 数据库的在缓存中找到的磁盘块请求数 |
pg_stat_get_numscans(oid) | bigint | 参数为表时是完成的顺序扫描数,参数为索引时是完成的索引扫描数 |
pg_stat_get_tuples_returned(oid) | bigint | 参数为表时顺序扫描读取的元组数,或参数为索引时读取的索引元组数 |
pg_stat_get_tuples_fetched(oid) | bigint | 参数为表时顺序扫描取出的有效(未过期)表元组数,或参数为索引时用该索引的索引扫描取出的元组数 |
pg_stat_get_tuples_inserted(oid) | bigint | 插入表的元组数 |
pg_stat_get_tuples_updated(oid) | bigint | 表中更新的元组数 |
pg_stat_get_tuples_deleted(oid) | bigint | 从表中删除的元组数 |
pg_stat_get_blocks_fetched(oid) | bigint | 表或索引的磁盘块读取请求数 |
pg_stat_get_blocks_hit(oid) | bigint | 表或索引的在缓存中找到的磁盘块请求数 |
pg_stat_get_backend_idset() | set of integer | 当前活动后端 ID 的集合(从 1 到 N,N 为活动后端数)。用法示例见下文。 |
pg_stat_get_backend_pid(integer) | integer | 后端进程的 PID |
pg_stat_get_backend_dbid(integer) | oid | 后端进程的数据库 ID |
pg_stat_get_backend_userid(integer) | oid | 后端进程的用户 ID |
pg_stat_get_backend_activity(integer) | text | 后端进程的当前查询(调用者不是超级用户时为 NULL) |
注意:blocks_fetched 减去 blocks_hit 得到对该表、索引或数据库发出的内核 read() 调用数;但由于内核级缓冲,实际物理读取数通常更低。
函数 pg_stat_get_backend_idset 提供一种为每个活动后端生成一行的便捷方法。例如,要显示所有后端的 PID 和当前查询:
SELECT pg_stat_get_backend_pid(S.backendid) AS procpid,
pg_stat_get_backend_activity(S.backendid) AS current_query
FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS S;