2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2

27.5. 动态跟踪 #

PostgreSQL 提供了支持数据库服务器动态跟踪的设施。这允许外部实用程序在代码的特定点被调用,从而跟踪执行。

源代码中已经插入了许多探测点或跟踪点。这些探测点供数据库开发人员和管理员使用。默认情况下,探测点不会编译到 PostgreSQL 中;用户需要显式告诉 configure 脚本使探测点可用。

目前支持 DTrace 实用程序,该实用程序在撰写本文时可用于 Solaris、macOS、FreeBSD、NetBSD 和 Oracle Linux。Linux 的 SystemTap 项目提供了 DTrace 的等效功能,也可以使用。理论上,通过修改 src/include/utils/probes.h 中的宏定义,可以支持其他动态跟踪实用程序。

27.5.1. 编译动态跟踪 #

默认情况下,探测点不可用,因此您需要显式告诉 configure 脚本在 PostgreSQL 中使探测点可用。要包含 DTrace 支持,请在 configure 时指定 --enable-dtrace。有关更多信息,请参见 第 17.3.3.6 节

27.5.2. 内置探测点 #

源代码中提供了许多标准探测点,如表 27.49 所示;表 27.50 显示了探测点中使用的类型。当然可以添加更多探测点来增强 PostgreSQL 的可观测性。

表 27.49. 内置 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 *) 查询重写开始时触发的探测点。arg0 是查询字符串。
query-rewrite-done (const char *) 查询重写完成时触发的探测点。arg0 是查询字符串。
query-plan-start () 查询规划开始时触发的探测点。
query-plan-done () 查询规划完成时触发的探测点。
query-execute-start () 查询执行开始时触发的探测点。
query-execute-done () 查询执行完成时触发的探测点。
statement-status (const char *) 服务器进程更新其 pg_stat_activity.status 时触发的探测点。arg0 是新的状态字符串。
checkpoint-start (int) 检查点开始时触发的探测点。arg0 包含用于区分不同检查点类型的位标志,例如关闭、立即或强制。
checkpoint-done (int, int, int, int, int) 检查点完成时触发的探测点。(接下来的探测点在检查点处理过程中按顺序触发。)arg0 是写入的缓冲区数量。arg1 是缓冲区的总数。arg2、arg3 和 arg4 分别包含添加、删除和回收的 WAL 文件数量。
clog-checkpoint-start (bool) 检查点 CLOG 部分开始时触发的探测点。arg0 对于正常检查点为 true,对于关闭检查点为 false。
clog-checkpoint-done (bool) 检查点 CLOG 部分完成时触发的探测点。arg0 的含义与 clog-checkpoint-start 相同。
subtrans-checkpoint-start (bool) 检查点 SUBTRANS 部分开始时触发的探测点。arg0 对于正常检查点为 true,对于关闭检查点为 false。
subtrans-checkpoint-done (bool) 检查点 SUBTRANS 部分完成时触发的探测点。arg0 的含义与 subtrans-checkpoint-start 相同。
multixact-checkpoint-start (bool) 检查点 MultiXact 部分开始时触发的探测点。arg0 对于正常检查点为 true,对于关闭检查点为 false。
multixact-checkpoint-done (bool) 检查点 MultiXact 部分完成时触发的探测点。arg0 的含义与 multixact-checkpoint-start 相同。
buffer-checkpoint-start (int) 检查点缓冲区写入部分开始时触发的探测点。arg0 包含用于区分不同检查点类型的位标志,例如关闭、立即或强制。
buffer-sync-start (int, int) 在检查点期间开始写入脏缓冲区时触发的探测点(在确定哪些缓冲区需要写入后)。arg0 是缓冲区的总数。arg1 是当前脏污并需要写入的缓冲区数量。
buffer-sync-written (int) 检查点期间每次写入缓冲区后触发的探测点。arg0 是缓冲区的 ID 号。
buffer-sync-done (int, int, int) 所有脏缓冲区都已写入后触发的探测点。arg0 是缓冲区的总数。arg1 是检查点进程实际写入的缓冲区数量。arg2 是预期写入的数量(buffer-sync-start 的 arg1);任何差异都反映了其他进程在检查点期间刷新缓冲区。
buffer-checkpoint-sync-start () 在将脏缓冲区写入内核后,开始发出 fsync 请求之前触发的探测点。
buffer-checkpoint-done () 缓冲区与磁盘同步完成时触发的探测点。
twophase-checkpoint-start () 检查点两阶段部分开始时触发的探测点。
twophase-checkpoint-done () 检查点两阶段部分完成时触发的探测点。
buffer-extend-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int) 关系扩展开始时触发的探测点。arg0 包含要扩展的 fork。arg1、arg2 和 arg3 包含标识关系的表空间、数据库和关系 OID。arg4 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。arg5 是调用者希望扩展的块数。
buffer-extend-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int, BlockNumber) 关系扩展完成时触发的探测点。arg0 包含要扩展的 fork。arg1、arg2 和 arg3 包含标识关系的表空间、数据库和关系 OID。arg4 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。arg5 是关系扩展的块数,由于资源限制,这可能小于 buffer-extend-start 中的数量。arg6 包含第一个新块的 BlockNumber。
buffer-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 开始读取缓冲区时触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。
buffer-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) 读取缓冲区完成时触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。arg6 如果缓冲区在池中找到则为 true,否则为 false。
buffer-flush-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 在发出共享缓冲区的任何写入请求之前触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。
buffer-flush-done (ForkNumber, BlockNumber, Oid, Oid, Oid) 写入请求完成时触发的探测点。(注意,这仅反映了将数据传递给内核的时间;通常尚未实际写入磁盘。)参数与 buffer-flush-start 相同。
wal-buffer-write-dirty-start () 当服务器进程开始写入脏 WAL 缓冲区,因为没有更多 WAL 缓冲区空间可用时触发的探测点。(如果经常发生这种情况,则表明 wal_buffers 太小。)
wal-buffer-write-dirty-done () 脏 WAL 缓冲区写入完成时触发的探测点。
wal-insert (unsigned char, unsigned char) 插入 WAL 记录时触发的探测点。arg0 是记录的资源管理器(rmid)。arg1 包含信息标志。
wal-switch () 请求 WAL 段切换时触发的探测点。
smgr-md-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 开始从关系中读取块时触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。
smgr-md-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) 读取块完成时触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。arg6 是实际读取的字节数,而 arg7 是请求的字节数(如果它们不同,则表示读取不足)。
smgr-md-write-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 开始将块写入关系时触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。
smgr-md-write-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) 写入块完成时触发的探测点。arg0 和 arg1 包含页的 fork 和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是创建本地缓冲区临时关系(或 INVALID_PROC_NUMBER (-1) 用于共享缓冲区)的后端 ID。arg6 是实际写入的字节数,而 arg7 是请求的字节数(如果它们不同,则表示写入不足)。
sort-start (int, bool, int, int, bool, int) 排序操作开始时触发的探测点。arg0 指示堆、索引或基数排序。arg1 对于唯一值强制为 true。arg2 是键列的数量。arg3 是允许的工作内存的千字节数。arg4 如果需要对排序结果进行随机访问,则为 true。arg5 指示串行(0)、并行工作进程(1)或并行领导者(2)。
sort-done (bool, long) 排序完成时触发的探测点。arg0 对于外部排序为 true,对于内部排序为 false。arg1 是外部排序使用的磁盘块数,或内部排序使用的内存千字节数。
lwlock-acquire (char *, LWLockMode) 获取 LWLock 时触发的探测点。arg0 是 LWLock 的分片。arg1 是请求的锁模式,可以是独占或共享。
lwlock-release (char *) 释放 LWLock 时触发的探测点(但请注意,任何已释放的等待者尚未被唤醒)。arg0 是 LWLock 的分片。
lwlock-wait-start (char *, LWLockMode) LWLock 未立即可用,并且服务器进程已开始等待锁可用时触发的探测点。arg0 是 LWLock 的分片。arg1 是请求的锁模式,可以是独占或共享。
lwlock-wait-done (char *, LWLockMode) 服务器进程从等待 LWLock 中释放时触发的探测点(尚未实际获得锁)。arg0 是 LWLock 的分片。arg1 是请求的锁模式,可以是独占或共享。
lwlock-condacquire (char *, LWLockMode) 当调用者指定不等待时,成功获取 LWLock 时触发的探测点。arg0 是 LWLock 的分片。arg1 是请求的锁模式,可以是独占或共享。
lwlock-condacquire-fail (char *, LWLockMode) 当调用者指定不等待时,未能成功获取 LWLock 时触发的探测点。arg0 是 LWLock 的分片。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 () 死锁检测器发现死锁时触发的探测点。

表 27.50. 探测点参数中使用的定义类型

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

27.5.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 脚本需要仔细编写和调试,否则收集到的跟踪信息可能毫无意义。在大多数发现问题的情况下,是仪器化出了问题,而不是底层系统。在讨论使用动态跟踪找到的信息时,请务必附上用于允许检查和讨论脚本的脚本。

27.5.4. 定义新探测点 #

开发人员可以在代码中的任何需要的地方定义新探测点,但这将需要重新编译。以下是插入新探测点的步骤:

  1. 确定探测点名称和通过探测点提供的数据

  2. 将探测点定义添加到 src/backend/utils/probes.d

  3. 如果包含 pg_trace.h 的模块中尚未包含它,请包含它,并在源代码中所需的点插入 TRACE_POSTGRESQL 探测点宏。

  4. 重新编译并验证新探测点是否可用。

示例:  以下是如何添加一个探测点来通过事务 ID 跟踪所有新事务的示例。

  1. 确定探测点将命名为 transaction-start,并且需要 LocalTransactionId 类型的参数。

  2. 将探测点定义添加到 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 宏。

提交更正

如果您在文档中发现任何不正确之处、与您对特定功能的实际体验不符之处,或需要进一步澄清的地方,请使用 此表单 报告文档问题。