9.1第二十七章

From PostgreSQL wiki
Jump to navigationJump to search

Chapter 27. 监控数据库的活动

数据库管理员通常想知道的就是“数据库现在在干什么?”本章将讲述如何做到这一点。

有很多工具可以用来监控数据库的行为和分析性能。本章的大部分都致力于PostgreSQL的统计信息收集器,但我们不能忽视UNIX系统的常规监控程序例如ps,top,iostat和vmstat,而且,一旦定位了一个低性能的查询,可能需要使用PostgreSQL的EXPLAIN命令做进一步的研究。14.1小节讨论了EXPLAIN以及其他的方法来理解单个查询的行为。

27.1. 标准Unix工具

在大多数Unix系统中,PostgreSQL修改了ps命令汇报的命令行标题,所以每个服务进程都可以被容易识别。一个示例显示如下:

 $ ps auxww | grep ^postgres
 postgres   960  0.0  1.1  6104 1480 pts/1    SN   13:17   0:00 postgres -i
 postgres   963  0.0  1.1  7084 1472 pts/1    SN   13:17   0:00 postgres: writer process
 postgres   965  0.0  1.1  6152 1512 pts/1    SN   13:17   0:00 postgres: stats collector process
 postgres   998  0.0  2.3  6532 2992 pts/1    SN   13:18   0:00 postgres: tgl runbug 127.0.0.1 idle
 postgres  1003  0.0  2.4  6532 3128 pts/1    SN   13:19   0:00 postgres: tgl regression [local] SELECT waiting
 postgres  1016  0.1  2.4  6532 3080 pts/1    SN   13:19   0:00 postgres: tgl regression [local] idle in transaction

(在不同的平台中都可以通过适当的方法显示进程的细节,就像上面显示的那样。本例来源于较新的 Linux 系统。)这里列出的第一个进程是主服务进程。显示的它的命令参数正是用来启动它的参数。接下来的两个进程是由主服务进程自动启动的两个后台工作进程。(如果你设置系统为不启动统计信息收集器,“stats collector”进程将不会出现。)剩下的每一个进程都是一个用来处理客户端连接的服务进程。每个这类进程设置它的命令行显示为以下格式

 postgres:  用户 数据库 主机 活动

用户,数据库和(客户端)主机在客户端连接生命期内保持相同,但活动描述符会改变。活动可能是idle(空闲,例如等待客户端的命令),idle in transaction(在事务中的空闲状态,在 BEGIN 段中等待客户端的下一步操作),或者类似于 SELECT 的命令名。还有就是 waiting 状态,表示服务进程当前在等待由另一个会话控制的锁。在以上的示例中,我们可以发现进程 1003 正在等待进程 1016 完成它的事务并释放一些锁。

如果你关闭了 update_process_title ,那么行为指示器将不再更新;进程的标题只在新进程启动的时候被设置一次。在某些平台中,这为每个命令节省一定量的开销;而在其他的平台则不明显。

提示:Solaris需要特殊处理。你必须使用 /usr/ucb/ps 而不是/bin/ps。你还要使用两个 w 标志,而不是一个。另外,你最初调用
postmaster 时用到的命令行在 ps 状态显示中必须比 ps 给每个服务器进程显示的短。如果没满足这三个条件, 那么 ps 为每个服务
器进程输出的将是最初的 postmaster 的命令行。

27.2. 统计信息收集器

PostgreSQL 的统计信息收集器是一个支持收集和汇报服务器活动信息的子系统。目前,这个收集器可以给对表和索引的访问计数,包括磁盘块的数量和独立行的项。它还可以跟踪每个表中的行数,对每个表执行vacuum和analyze的情况。它还可以统计对用户定义的每个函数的调用次数和总的耗时。

PostgreSQL 还能够报告其他服务进程当前正在执行的命令。这是一个和收集器进程无关的能力。

27.2.1.统计信息收集的配置

因为收集统计信息对执行查询需要增加一些开销,所以系统可以设置为收集或者不收集信息。这通常是由 postgresql.conf 文件中的配置参数控制(参考第十八章查看关于设置配置参数的细节)。

参数 track_counts 控制是否收集对表和索引访问的统计信息。

参数 track_functions 启用对用户自定义函数的跟踪。

参数 track_activities 启用对任何服务进程当前执行的命令的监控。


通常这些参数在 postgresql.conf 文件中设置,这样就可以应用到所有的服务器进程中,但是我们也可以通过 SET 命令在单独的会话中关闭它们(为了避免普通用户隐藏他们的行为不给超级管理员看,只有超级管理员才允许使用SET命令改变这些参数)。

统计信息收集器通过临时文件与后端程序交互信息(包括autovacuum)。这些文件存储在 pg_stat_tmp 子目录中。当 postmaster 停止后,一个持久化的统计信息的拷贝被存在 global 子目录中。为了提升性能,参数 stats_temp_directory 可以用来指向一个基于内存的文件系统,用来降低对物理 I/O 的需求。

27.2.2. 查看收集的统计信息

表27-1中列出的一些预定义的视图,可以用于显示收集的统计数据。另外,也可以使用以下的统计函数建立自定义视图。

当使用统计数据来监控当前的行为时,需要知道这些统计数据不是实时更新的。每个独立的服务进程在空闲前才传送最新的统计计数到收集器;所以一个查询或者进行中的事务不会影响显示的总数的。而且,收集器每隔 PGSTAT_STAT_INTERVAL(默认值为500)毫秒的周期生成一个新的报表。所以显示的信息落后于实际的行为。但是,通过 track_activities 收集的当前查询的信息收集总是最新的。

另一个重点是当一个服务进程被请求显示这些统计数据的任何时候,它首先获取收集进程生成的最新的报表信息,然后在统计视图和函数中一直使用这个快照直到在当前事务结束。因此统计在当前事务的持续期间内是不会改变的。类似的,关于所有会话的当前查询的信息只在首次请求时被收集一次,之后在整个事务中,将一直使用相同的信息快照。这是一个功能,而不是一个 bug,因为这允许你执行很多查询而不用担心数字背着你被改变。但是如果你想看到每个查询的新的结果,记住不要在任何事务块中进行这些查询。另外,你可以调用 pg_stat_clear_snapshot(),它将忽略当前事务的统计信息快照(如果有的话)。下一次使用统计信息的时候,将生成一个新的快照。

表27-1. 标准统计视图

视图名 描述
pg_stat_activity 每个服务进程一行,显示数据库OID,数据库名,进程ID,用户OID,用户名,应用程序名,客户端地址和端口,服务进程、当前事务和当前查询开始执行的时间,进程的等待状态以及当前查询的文本。除非track_activities被关闭,否则还会显示当前查询中的数据的列。而且,只有查看这个视图的用户是超级用户或者拥有这个被报告的进程的用户才能看到这些列。
pg_stat_bgwriter 只有一行,显示background writer的cluster-wide的统计数据:计划的checkpoints,已请求的checkpoints,checkpoints写入的缓冲区以及清空扫描,以及background writer因为写入太多缓冲区而停止一个清空扫描的次数。还包括关于shared buffer pool的统计数据,包括其他后台写入的缓冲区(不是由background writer写入的部分)以及总共分配的缓冲区。
pg_stat_database 每个数据库一行,显示数据库OID,数据库名,连接到该数据库的服务进程数,这个数据库中提交和回滚的事务数,磁盘块读取数,缓冲区命中(例如,通过在缓冲区中找到了块而避免的块读请求),返回,获取,插入、更新和删除的行数。
pg_stat_database_conflicts One row per database, showing database OID, database name and the number of queries that have been canceled in this database due to dropped tablespaces, lock timeouts, old snapshots, pinned buffers and deadlocks. Will only contain information on standby servers, since conflicts do not occur on master servers.
pg_stat_replication One row per WAL sender process, showing process ID, user OID, user name, application name, client's address, host name (if available) and port number, time at which the server process began execution, and the current WAL sender state and transaction log location. In addition, the standby reports the last transaction log position it received and wrote, the last position it flushed to disk, and the last position it replayed, and this information is also displayed here. If the standby's application names matches one of the settings in synchronous_standby_names then the sync_priority is shown here also, that is the order in which standbys will become the synchronous standby. The columns detailing what exactly the connection is doing are only visible if the user examining the view is a superuser. The client's host name will be available only if log_hostname is set or if the user's host name needed to be looked up during pg_hba.conf processing.
pg_stat_all_tables 当前数据库中每个表一行(包括 TOAST 表),表的 OID, 模式和表名字,发起的顺序扫描的总数,顺序扫描抓取的live数据行的数目,发起的索引扫描的总数(属于该表的所有索引),索引扫描抓取的live数据行的数目,以及插入,更新,和删除的行的总数,被HOT更新的行数(例如,没有额外的索引更新),live的和dead的记录数,最近一次通过手动进行non-FULL vacuum的时间,最近一次通过autovacuum守护进程进行vacuum的时间,最近一次被手动analyze的时间,最近一次通过autovacuum守护进程进行analyze的时间,通过手动进行non-FULL vacuum的次数,通过autovacuum守护进程进行vacuum的次数,手动analyze的次数和通过autovacuum守护进程进行analyze的次数。
pg_stat_sys_tables 和 pg_stat_all_tables 一样,只不过只显示系统表。
pg_stat_user_tables 和 pg_stat_all_tables 一样,只不过只显示用户表。
pg_stat_xact_all_tables Similar to pg_stat_all_tables, but counts actions taken so far within the current transaction (which are not yet included in pg_stat_all_tables and related views). The columns for numbers of live and dead rows and vacuum and analyze actions are not present in this view.
pg_stat_xact_sys_tables Same as pg_stat_xact_all_tables, except that only system tables are shown.
pg_stat_xact_user_tables Same as pg_stat_xact_all_tables, except that only user tables are shown.
pg_stat_all_indexes 对当前数据库的每个索引,显示索引所在表和索引的 OID, schema,表和索引名,包括使用了该索引的索引扫描总数,索引扫描返回的索引记录的数目,使用该索引的简单索引扫描抓取的表中的live row行数。
pg_stat_sys_indexes 和 pg_stat_all_indexes 一样,只不过只显示系统表上的索引。
pg_stat_user_indexes 和 pg_stat_all_indexes 一样,只不过只显示用户表上的索引。
pg_statio_all_tables 当前数据库中每个表一行(包括 TOAST 表),包含表的 OID,schema名和表名,包含从该表中读取的磁盘块总数,缓冲区命中的次数,在该表上所有索引的磁盘块读取和缓冲区命中总数,在该表的辅助 TOAST 表(如果存在)上的磁盘块读取和缓冲区命中总数,以及 TOAST 表的索引的磁盘块读取和缓冲区命中总数。
pg_statio_sys_tables 和 pg_statio_all_tables 一样,只不过只显示系统表。
pg_statio_user_tables 和 pg_statio_all_tables 一样,只不过只显示用户表。
pg_statio_all_indexes 当前数据库中每个索引一行,包含表和索引OID,模式,表和索引名,该索引的磁盘块读取和缓冲区命中的次数。
pg_statio_sys_indexes 和 pg_statio_all_indexes 一样,只不过只显示系统表。
pg_statio_user_indexes 和 pg_statio_all_indexes 一样,只不过只显示用户表。
pg_statio_all_sequences 当前数据库中每个序列对象一行,包含序列的OID,模式和序列名,序列的磁盘读取和缓冲区命中的次数。
pg_statio_sys_sequences 和 pg_statio_all_sequences 一样,只不过只显示系统序列对象。(目前我们没有定义系统序列对象,所以这个视图总是空的。)
pg_statio_user_sequences 和 pg_statio_all_sequences 一样,只不过只显示用户序列对象。
pg_stat_user_functions 每个被跟踪的函数一行,包含函数OID,schema,名称,调用次数,总调用时间和本身耗费时间。本身耗费时间是函数本身花费的时间,总调用时间还包含它调用的函数。时间值的单位为毫秒。
pg_stat_xact_user_functions Similar to pg_stat_user_functions, but counts only calls during the current transaction (which are not yet included in pg_stat_user_functions).

每条索引的统计数据对于判断哪个索引被使用以及它们的效果非常有用。

从 PostgreSQL 8.1 开始,索引既可以直接使用,也可以通过"位图扫描"使用。在位图扫描中,多个索引的输出可以通过 AND 或者 OR 规则合并;所以,很难把单个的堆(表)行抓取和某个特定的索引关联起来。因此,位图扫描增大它使用的 pg_stat_all_indexes.idx_tup_read 计数。并且它还增加为表使用的 pg_stat_all_tables.idx_tup_fetch 计数,但是它并不会影响 pg_stat_all_indexes.idx_tup_fetch。

 注:在 PostgreSQL 8.1 之前,idx_tup_read 和 idx_tup_fetch 计数实际上总是一样的。现在即使是不考虑位图扫描,它们也
 可能是不同的,因为 idx_tup_read 记录从索引检索的记录条目,而 idx_tup_fetch 记录从表中抓取的live行数;如果有已经失效
 的或者是还未提交的行通过索引扫描找出来,后者将会小一些。

pg_statio_ 系列视图在判断缓冲的效果的时候特别有用。在实际磁盘读取远比缓冲命中小的时候,我们就知道这个缓冲基本满足大多数读要求,因此不需要进行内核调用。但是,这些统计并未给出所有信息:由于 PostgreSQL 处理磁盘的方式,不在 PostgreSQL 缓冲区缓存的数据可能仍然驻留在内核的 I/O 缓存中,因此仍然可能不经过物理读取而抓取。对获取 PostgreSQL 的 I/O 行为的更多细节感兴趣的用户可以结合使用 PostgreSQL 的统计收集器和可以分析内核 I/O 处理的操作系统工具来获取更多细节。

其它查看统计的方法可以通过书写使用底层统计访问函数的查询来设置,这些底层统计访问函数和标准视图里使用的是一样的。这些函数在 表 27-2 中列出。就某数据库进行访问的函数接受一个数据库 OID 为参数来标识需要报告哪个数据库。就某表或者某索引进行访问的函数接受一个表或者索引的 OID 作为参数。函数调用的统计需要使用函数 OID 进行访问(请注意这些函数只能看到在当前数据库里的表、索引和函数)。就某服务器进行访问的函数接受一个服务进程号,其范围从一到当前活跃服务进程的数目。

表 27-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_db_tuples_returned(oid) bigint 数据库中返回的tuple数
pg_stat_get_db_tuples_fetched(oid) bigint 数据库中读取的tuple数
pg_stat_get_db_tuples_inserted(oid) bigint 数据库中插入过的tuple数
pg_stat_get_db_tuples_updated(oid) bigint 数据库中更新过的tuple数
pg_stat_get_db_tuples_deleted(oid) bigint 数据库中删除过的tuple数
pg_stat_get_db_conflict_tablespace(oid) bigint Number of queries canceled because of recovery conflict with dropped tablespaces in database
pg_stat_get_db_conflict_lock(oid) bigint Number of queries canceled because of recovery conflict with locks in database
pg_stat_get_db_conflict_snapshot(oid) bigint Number of queries canceled because of recovery conflict with old snapshots in database
pg_stat_get_db_conflict_bufferpin(oid) bigint Number of queries canceled because of recovery conflict with pinned buffers in database
pg_stat_get_db_conflict_startup_deadlock(oid) bigint Number of queries canceled because of recovery conflict with deadlocks in database
pg_stat_get_db_stat_reset_time(oid) timestamptz Time of the last statistics reset for the database. Initialized to the system time during the first connection to each database. The reset time is updated when you call pg_stat_reset on the database, as well as upon execution of pg_stat_reset_single_table_counters against any table or index in it.
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 表中更新过的行数(包含HOT更新)
pg_stat_get_tuples_deleted(oid) bigint 表中删除过的行数
pg_stat_get_tuples_hot_updated(oid) bigint 表中通过HOT-update更新过的行数
pg_stat_get_live_tuples(oid) bigint 表中的live row数
pg_stat_get_dead_tuples(oid) bigint 表中的dead row数
pg_stat_get_blocks_fetched(oid) bigint 表或者索引的磁盘块读取请求的数量
pg_stat_get_blocks_hit(oid) bigint 在缓冲区中找到的表或者索引的磁盘块请求数目
pg_stat_get_last_vacuum_time(oid) timestamptz 该表中用户手工启动的vacuum的最后时间
pg_stat_get_last_autovacuum_time(oid) timestamptz 该表中由autovacuum守护进程启动的vacuum的最后时间
pg_stat_get_last_analyze_time(oid) timestamptz 该表中由用户启动的分析的最后时间
pg_stat_get_last_autoanalyze_time(oid) timestamptz 该表中由autovacuum守护进程启动的分析的最后时间
pg_stat_get_vacuum_count(oid) bigint The number of times this table has been non-FULL vacuumed manually
pg_stat_get_autovacuum_count(oid) bigint The number of times this table has been vacuumed by the autovacuum daemon
pg_stat_get_analyze_count(oid) bigint The number of times this table has been analyzed manually
pg_stat_get_autoanalyze_count(oid) bigint The number of times this table has been analyzed by the autovacuum daemon
pg_stat_get_xact_numscans(oid) bigint Number of sequential scans done when argument is a table, or number of index scans done when argument is an index, in the current transaction
pg_stat_get_xact_tuples_returned(oid) bigint Number of rows read by sequential scans when argument is a table, or number of index entries returned when argument is an index, in the current transaction
pg_stat_get_xact_tuples_fetched(oid) bigint Number of table rows fetched by bitmap scans when argument is a table, or table rows fetched by simple index scans using the index when argument is an index, in the current transaction
pg_stat_get_xact_tuples_inserted(oid) bigint Number of rows inserted into table, in the current transaction
pg_stat_get_xact_tuples_updated(oid) bigint Number of rows updated in table (includes HOT updates), in the current transaction
pg_stat_get_xact_tuples_deleted(oid) bigint Number of rows deleted from table, in the current transaction
pg_stat_get_xact_tuples_hot_updated(oid) bigint Number of rows HOT-updated in table, in the current transaction
pg_stat_get_xact_blocks_fetched(oid) bigint Number of disk block fetch requests for table or index, in the current transaction
pg_stat_get_xact_blocks_hit(oid) bigint Number of disk block requests found in cache for table or index, in the current transaction
pg_backend_pid() integer 当前会话对应的服务进程的进程ID
pg_stat_get_activity(integer) setof record 如果指定PID则返回该PID对应后台进程的信息,如果传入NULL则为每一条后台活跃进程返回一条信息。返回结果为pg_stat_activity视图的子集。
pg_stat_get_function_calls(oid) bigint 该函数被调用的次数
pg_stat_get_function_time(oid) bigint 调用该函数所耗用的所有时间,单位为毫秒。包括它调用其他函数的时间。
pg_stat_get_function_self_time(oid) bigint 仅仅该函数调用使用的时间,不包括它调用其他函数耗费的时间。
pg_stat_get_xact_function_calls(oid) bigint Number of times the function has been called, in the current transaction.
pg_stat_get_xact_function_time(oid) bigint Total wall clock time spent in the function, in microseconds, in the current transaction. Includes the time spent in functions called by this one.
pg_stat_get_xact_function_self_time(oid) bigint Time spent in only this function, in the current transaction. Time spent in called functions is excluded.
pg_stat_get_backend_idset() setof integer 当前活跃服务器编号的集合(从 1 到活跃后端的数目)。 参阅文本中的使用样例。
pg_stat_get_backend_pid(integer) integer 给定服务进程的进程 ID
pg_stat_get_backend_dbid(integer) oid 给出服务进程的数据库 ID
pg_stat_get_backend_userid(integer) oid 给出服务进程的用户 ID
pg_stat_get_backend_activity(integer) text 给出服务进程当前运行的命令,条件是当前用户为超级用户或者执行这个会话的用户(并且需要打开track_activities)
pg_stat_get_backend_waiting(integer) boolean 如果给出的服务进程在等待一个锁则为TRUE,条件是当前用户为超级用户或者执行这个会话的用户(并且需要打开track_activities)
pg_stat_get_backend_activity_start(integer) timestamp with time zone 给出的服务进程执行的查询的开始时间,条件是当前用户为超级用户或者执行这个会话的用户(并且需要打开track_activities)
pg_stat_get_backend_xact_start(integer) timestamp with time zone 给出的服务进程当前运行的事务的开始时间,条件是当前用户为超级用户或者执行这个会话的用户(并且需要打开track_activities)
pg_stat_get_backend_start(integer) timestamp with time zone 给出的服务器进程启动的时间,如果当前用户不是超级用户,也不是被查询的后端的同一个用户,则为空。
pg_stat_get_backend_client_addr(integer) inet 连接到给定服务器的客户端的 IP 地址。如果连接是建立在 Unix 域套接字上的,那么为空。如果当前用户不是超级用户,也不是被查询的会话的使用用户,也为空。
pg_stat_get_backend_client_port(integer) integer 连接到给定服务器的客户端的 IP 端口。如果连接是建立在 Unix 域套接字上的,那么为 -1。如果当前用户不是超级用户,也不是被查询的会话的使用用户,为空。
pg_stat_get_bgwriter_timed_checkpoints() bigint background writer启动由超时触发的checkpoint的次数(因为对于checkpoint_timeout已经超时)
pg_stat_get_bgwriter_requested_checkpoints() bigint 由于到达checkpoint_segements的限制或者CHECKPOINT命令被调用而引起的background writer 启动checkpoint的次数
pg_stat_get_bgwriter_buf_written_checkpoints() bigint 在checkpoint期间由background writer写入的buffer数
pg_stat_get_bgwriter_buf_written_clean() bigint 由于例行清除脏页引起的background writer写入的buffer数
pg_stat_get_bgwriter_maxwritten_clean() timestamptz Time of the last statistics reset for the background writer, updated when executing pg_stat_reset_shared('bgwriter') on the database cluster.
pg_stat_get_bgwriter_stat_reset_time() bigint 因为写入了超过bgwriter_lru_maxpages参数限制的buffer数量而导致background writer停止的cleaning scan的次数
pg_stat_get_buf_written_backend() bigint 因为需要分配新的buffer而引起的后台进程写入buffer的次数
pg_stat_get_buf_alloc() setof record One record for each active wal sender. The fields returned are a subset of those in the pg_stat_replication view.
pg_stat_get_wal_senders() bigint 总共分配的buffer数
pg_stat_clear_snapshot() void 忽略当前的统计信息
pg_stat_reset() void 重置当前数据库的所有统计计数器为零(需要超级管理员权限)
pg_stat_reset_shared(text) void 重置一些数据库集群的共享的统计计数器为零(需要超级管理员权限)。调用pg_stat_reset_shared('bgwriter')将清零所有的由pg_stat_bgwriter显示的值。
pg_stat_reset_single_table_counters(oid) void 重置当前数据库中单个表或者索引的统计计数为零(需要超级管理员权限)
pg_stat_reset_single_function_counters(oid) void 重置当前数据库中单个函数的统计计数为零(需要超级管理员权限)
 注:pg_stat_get_blocks_fetched 减去 pg_stat_get_blocks_hit 将获得给出的表、索引或者数据库执行的内核read()数;
 但由于内核级的缓存,实际上的物理读会更低。*_blks_read系列统计信息列使用这个减法,例如,读取减去命中。

所有访问关于后台进程的信息通常都以后台进程编号做索引,除了pg_stat_get_activity使用PID做索引。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;

27.3. 查看锁

监控数据库活动的另外一个有用的工具是系统表 pg_locks。这样就允许数据库管理员查看在锁管理器里面的未决的锁的信息。比如,这个功能可以用于:

  • 查看当前所有未决锁,所有在某一特定数据库里的关系上的锁, 所有在特定关系上的锁,或者某一 PostgreSQL 会话持有的所有锁。
  • 判断当前数据库里带有最多未批准锁的关系(它很可能是数据库客户端的竞争源)。
  • 判断锁竞争给数据库性能带来的影响,以及锁竞争随着整个数据库流量的变化所产生的变化。

pg_locks 视图的细节在 Section 45.50 里。有关更多 PostgreSQL 的锁和管理并发性的信息,请参考 Chapter 13。

27.4. 动态跟踪

PostgreSQL提供机制来支持动态追踪数据库服务器。这允许一个外部的工具在代码中的特殊位置被调用来用于追踪执行过程。

大量的探针和追踪点已经被插入到源码中。这些探针被扩展给数据库开发人员和管理员使用。默认情况下这些探针不被编译到PostgreSQL中;用户需要显示的告诉configure脚本需要提供探针功能。

当前,仅支持DTrace工具,它存在于OpenSolaris,Solaris 10和Mac OS X的Leopard中。我们期待将来的FreeBSD以及还可能有其他的操作系统都提供DTrace工具。Linux的SystemTap项目也提供相当于DTrace的功能。理论上说,修改src/include/utils/probes.h中的宏定义也可以支持其他的动态追踪工具。

27.4.1. 编译代码以支持动态追踪

跟踪点是默认禁止的,你必须在运行 configure 脚本时明确使用 --enable-dtrace 选项来启用 DTrace 支持。需要了解更多信息请参考 15.5 章。

27.4.2. 内置探针

源码中提供大量的标准探针,列在表27-3中。当然也可以添加更多的探针来增强PostgreSQL的可观测性。


表 27-3. 内置的 DTrace 探针

名称 参数 描述
transaction-start (LocalTransactionId) 在开始新事务的时候被触发的探针。arg0 为事务 ID 。
transaction-commit (LocalTransactionId) 在事务成功完成时被触发的探针。arg0 为事务 ID 。
transaction-abort (LocalTransactionId) 在事务完成且未执行成功时被触发的探针。arg0 为事务 ID 。
query-start (const char *) 在查询开始的时候被触发的探针。arg0 为查询字符串。
query-done (const char *) 在查询完成的时候被触发的探针。arg0 为查询字符串。
query-parse-start (const char *) 在查询分析开始的时候被触发的探针。arg0 为查询字符串。
query-parse-done (const char *) 在查询分析完成的时候被触发的探针。arg0 为查询字符串。
query-rewrite-start (const char *) 在查询rewriting开始的时候被触发的探针。arg0 为查询字符串。
query-rewrite-done (const char *) 在查询rewriting完成的时候被触发的探针。arg0 为查询字符串。
query-plan-start () 在生成执行计划开始的时候被触发的探针。
query-plan-done () 在生成执行计划结束的时候被触发的探针。
query-execute-start () 查询执行开始的时候触发的探针。
query-execute-done () 查询执行结束的时候触发的探针。
statement-status (const char *) 在服务进程更新 pg_stat_activity 的任何时候触发的探针。arg0 为状态字符串。
checkpoint-start (int) 在一个 checkpoint 操作开始的时候触发的探针。arg0 保存按位标识的标志,用于区别不同的 checkpoint 类型,例如关机,immediate 或者强制的checkpoint。
checkpoint-done (int, int, int, int, int) 在一个 checkpoint完成时触发的探针。(探针会在checkpoint过程中列出下一个触发的序列号)arg0 为写入的 buffer 数。arg1 为总的buffer 数。arg2,arg3 和 arg4 分别包含添加、删除和回收的 xlog 文件数。
clog-checkpoint-start (bool) 在checkpoint的CLOG部分开始时被触发的探针。arg0 如果为 true 则表示普通的 checkpoint,否则为服务器停止时候的 checkpoint。
clog-checkpoint-done (bool) 在 checkpoint 的 CLOG 部分完成时被触发的探针。参数内容和 clog-checkpoint-start 一样。
subtrans-checkpoint-start (bool) 在 checkpoint 的 SUBTRANS 部分开始时触发的探针。arg0 如果为 true 则表示普通的 checkpoint,否则为服务器停止时候的 checkpoint。
subtrans-checkpoint-done (bool) 在 checkpoint 的 SUBTRANS 部分完成时被触发的探针。参数内容和 subtrans-checkpoint-start 一样。
multixact-checkpoint-start (bool) 在 checkpoint 的 MultiXact 部分开始时触发的探针。arg0 如果为 true 则表示普通的 checkpoint,否则为服务器停止时候的 checkpoint。
multixact-checkpoint-done (bool) 在 checkpoint 的 MultiXact 部分完成时触发的探针。参数内容和 multixact-checkpoint-start 一样。
buffer-checkpoint-start (int) 当 checkpoint 的 buffer-writing 部分开始时触发的探针。arg0 保存按位标识的标志用来区分不同的 checkpoint 类型,例如关机、immediate 或者强制的。
buffer-sync-start (int, int) 在 checkpoint 期间当我们开始写入脏数据 buffer 时触发的探针(在标记哪些 buffer 必须被写入后)。arg0 为总的 buffer 数。arg1 为当前脏的且必须写入的 buffer 数。
buffer-sync-written (int) 在 checkpoint 期间每个 buffer 被写入后触发的探针。arg0 为 buffer 的 ID 数。
buffer-sync-done (int, int, int) 在所有的脏 buffer 被写入后触发的探针。arg0 为 buffer 总数。arg1 为被 checkpoint 进程实际写入的 buffer 数。arg2 为我们期望写入的数量(buffer-sync-start 的 arg1 );如果两者不同说明有其他进程在 checkpoint 期间刷出了buffer。
buffer-checkpoint-sync-start () 在脏数据被写入到操作系统内核但在调用 fsync 前触发的探针。
buffer-checkpoint-done () 在同步 buffer 数据到磁盘后触发的探针。
twophase-checkpoint-start () 当 checkpoint 的 two-phase 部分开始的时候触发的探针。
twophase-checkpoint-done () 当 checkpoint 的 two-phase 部分结束的时候触发的探针。
buffer-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, bool, bool) Buffer 读开始的时候触发的探针。arg0 和 arg1 包含页面的 fork 数和块数( 但是在一个关系扩展请求的时候,arg1 将为 -1 )。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID。当从本地 buffer 读取数据则 arg5 为 true,否则为 false。当请求为关系扩展请求时,arg6 为 true,普通读则为 false。
buffer-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, bool, bool, bool) Buffer 读完成时触发的探针。arg0 和 arg1 包含页面的 fork 数和块数( 但是在一个关系扩展请求的时候,arg1 将为 -1 )。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID。当从本地 buffer 读取数据则 arg5 为 true,否则为 false。当请求为关系扩展请求时,arg6 为 true,普通读则为 false。如果从pool中找到了 buffer 则 arg7 为 true,否则为 false。
buffer-flush-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 在进行任何对shared buffer的写请求前触发的探针。arg0 和 arg1 包含页面的 fork 数和块数。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID 。
buffer-flush-done (ForkNumber, BlockNumber, Oid, Oid, Oid) 当 write 请求完成被触发的探针。(注意这仅仅反映传送数据到系统内核的时点;此时数据通常还没有真正到达磁盘)。参数和 buffer-flush-start 的内容一样。
buffer-write-dirty-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 当一个服务进程开始写一个脏 buffer 时被触发的探针。(如果它发生得太频繁,则意味着 shared_buffer 太小或者控制 bgwriter 的参数需要调整。)arg0 和 arg1 包含页面的 fork 数和块数。arg2、arg3 和 arg4 包含表空间、数据库和关系的 OID 。
buffer-write-dirty-done (ForkNumber, BlockNumber, Oid, Oid, Oid) 当脏 buffer 的写完成时触发的探针。参数和 buffer-write-dirty-start 相同。
wal-buffer-write-dirty-start () 当服务器进程因为没有更多的 WAL buffer 而开始写一个脏 WAL buffer 时触发的探针。(如果这经常发生,则意味着 wal_buffers 太小。)
wal-buffer-write-dirty-done () 当脏的 WAL buffer 被写完成时触发的探针。
xlog-insert (unsigned char, unsigned char) 当一条 WAL 记录被插入时触发的探针。arg0 为记录的资源管理编号(rmid)。arg1 包含 info 标识。
xlog-switch () 当请求切换到一个新的 WAL 段时触发的探针。
smgr-md-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 当开始从一个关系中读一个块时触发的探针。arg0 以及 arg1 包含页面的 fork 数和块数。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID。
smgr-md-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int) 关系的块读结束时触发的探针。arg0 以及 arg1 包含页面的 fork 数和块数。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID。arg5 包含实际读的字节数,而 arg6 为请求的字节数(如果他们不同,则表明出问题了)。
smgr-md-write-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 当开始往一个关系中写一个块时触发的探针。arg0 以及 arg1 包含页面的 fork 数和块数。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID。
smgr-md-write-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int) 关系的块写结束时触发的探针。arg0 以及 arg1 包含页面的 fork 数和块数。arg2,arg3 和 arg4 包含表空间、数据库和关系的 OID。arg5 包含实际读的字节数,而 arg6 为请求的字节数(如果他们不同,则表明出问题了)。
sort-start (int, bool, int, int, bool) 当一个排序操作开始时触发的探针。arg0 表示堆、索引或者 datum 排序。如果 arg1 为 true 则表示值是强制唯一的。arg2 表示关键列的数量。arg3 表示分配的内存数量(K字节为单位)。如果要求对结果进行随机访问则 arg4 为 true。
sort-done (bool, long) 当一个排序操作完成时触发的探针。如果是外部排序则 arg0 为 true。arg1 为用于外部排序的磁盘块的数量,或者为用于内部排序的内存数量(K字节为单位)。
lwlock-acquire (LWLockId, LWLockMode) 一个 LWLock 被请求时触发的探针。arg0 为 LWLock 的 ID。arg1 为请求的锁模式,可以为排他模式或共享模式。
lwlock-release (LWLockId) 一个 LWLock 被释放时触发的探针(但需要注意此时所有等待此锁的等待者都还没被释放)。arg0 为 LWLock 的 ID。
lwlock-wait-start (LWLockId, LWLockMode) 当一个 LWLock 当时不存在,服务进程需要开始等待锁的时候触发的探针。arg0 为 LWLock 的 ID。arg1 为请求的锁模式,可以为排他模式或共享模式。
lwlock-wait-done (LWLockId, LWLockMode) 当一个服务进程从其等待 LWLock 过程中被释放出来时触发的探针(它此时还没拥有此锁)。arg0 为 LWLock 的 ID。arg1 为请求的锁模式,可以为排他模式或共享模式。
lwlock-condacquire (LWLockId, LWLockMode) 当一个调用者以不等待的模式请求一个 LWLock 且成功时触发的探针。arg0 为 LWLock 的 ID。arg1 为请求的锁模式,可以为排他模式或共享模式。
lwlock-condacquire-fail (LWLockId, LWLockMode) 当一个调用者以不等待的模式请求一个 LWLock 且没有成功时触发的探针。arg0 为 LWLock 的 ID。arg1 为请求的锁模式,可以为排他模式或共享模式。
lock-wait-start (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) 当一个请求重量级锁(lmgr lock)时因为锁不存在而开始等待时触发的探针。arg0 到 arg3 为用于标记被锁定对象的标签字段。arg4 指出被锁定对象的类型。arg5 指出被请求的锁类型。
lock-wait-done (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) 当请求重量级锁(lmgr lock)的过程结束等待(例如获得了锁)时触发的探针。参数和 lock-wait-start 相同。
deadlock-found () 当死锁检测器检测到一个死锁时触发的探针。

Table 27-4. 用于作为探针参数的数据类型

类型 定义
LocalTransactionId unsigned int
LWLockId int
LWLockMode int
LOCKMODE int
BlockNumber unsigned int
Oid unsigned int
ForkNumber int
bool char

27.4.3. 探针的使用

以下示例显示了一个用于分析系统中的事务计数 DTrace 脚本,作为一个在一次性能测试后替代 pg_stat_database 的快照:

 #!/usr/sbin/dtrace -qs
 
 postgresql$1:::transaction-start
 {
       @start["Start"] = count();
       self->ts  = timestamp;
 }
 
 postgresql$1:::transaction-abort
 {
       @abort["Abort"] = count();
 }
 
 postgresql$1:::transaction-commit
 /self->ts/
 {
       @commit["Commit"] = count();
       @time["Total time (ns)"] = sum(timestamp - self->ts);
       self->ts=0;
 }

当执行后,示例的D脚本给出以下输出:

 # ./txn_count.d `pgrep -n postgres` or ./txn_count.d <PID>
 ^C
 
 Start                                          71
 Commit                                         70
 Total time (ns)                        2312105013
 注:SystemTap 使用一个与 DTrace 的追踪脚本不同的标记方法,虽然相关的追踪点是相同的。有一点需要注意的是在本例中,
 SystemTap 脚本必须在定义探针名时在本例中使用冒号的地方使用双下划线代替连接线(-)。这将在以后的 SystemTap 版本
 中得到修复。

你需要注意的是 DTrace 脚本需要被仔细的编写和调试,否则收集的追踪信息可能毫无意义。在大多数情况下,找到的问题是由于错误的测量方法,而不是相关的系统。当解析使用动态追中获得的信息时,请确保附上使用的脚本,以便同时也检查和分析它。

更多的示例脚本可以在 PgFoundry dtrace 项目中找到。

27.4.4. 定义新探针

开发人员可以在任何想要的地方定义新探针,但这需要一次重新编译。以下为定义新探针的步骤:

  1. 确定生成探针的名称和数据
  2. 添加探针定义数据到 src/backend/utils/probes.d
  3. 如果头文件 pg_trace.h 未被包含到使用探针指针的模块则在模块中包含它,并在源码中需要的位置插入探针宏 TRACE_POSTGRESQL
  4. 重新编译并确定探针生效

示例:以下为一个示例显示你怎样添加一个探针通过事务ID来追踪所有的新事务。

1. 确定探针名为 transaction-start 且需要一个类型为 LocalTransactionId 的参数
2. 添加探针的定义到 definition to src/backend/utils/probes.d 中:
 probe transaction__start(LocalTransactionId);
 注意在探针名中的双下划线的使用。在 DTrace 脚本中使用本探针,双下划线需要被替换为连接线,所以 transaction-start 为提交给用户的文档中的探针名。
3. 在编译期间,transaction__start 被替换为叫做 TRACE_POSTGRESQL_TRANSACTION_START 的宏(注意这里只有一个下划线),通过包含 pg_trace.h引用。添加对这个宏的调用到代码中的恰当位置。在本例中,它看起来可能像这样:
 TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
4. 在重新编译和运行程序后,可以通过执行 DTrace 的命令检查你新添加探针真是否有效。你可能会看到类似的输出:
 # dtrace -ln transaction-start
    ID    PROVIDER          MODULE           FUNCTION NAME
 18705 postgresql49878     postgres     StartTransactionCommand transaction-start
 18755 postgresql49877     postgres     StartTransactionCommand transaction-start
 18805 postgresql49876     postgres     StartTransactionCommand transaction-start
 18855 postgresql49875     postgres     StartTransactionCommand transaction-start
 18986 postgresql49873     postgres     StartTransactionCommand transaction-start

在添加追踪宏到C代码中时有此东西需要注意:

  • 你应该注意作为探针的参数的数据类型必须匹配使用在宏中的变量的数据类型。否则你会在编译时出错。
  • 在大多数平台中,如果在生成 PostgreSQL 时使用了 --enable-dtrace,传递给追踪宏的参数总是会在调用宏时被运算出来,即使当时没有在进行动态追踪。如果你仅仅需要报告少量的本地变量,这通常不需要担心。但要注意不要将成本昂贵的函数调用作为参数。如果你确实需要这么做,可以考虑先检查一下追踪是否已经启用:
 if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
     TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));

每个追踪宏有一个相对应的 ENABLED 宏。