PostgreSQL 教程: 检查长时间运行的事务

二月 4, 2026

摘要:在本教程中,您将学习如何在 PostgreSQL 中检查长时间运行的事务。

目录

长时间运行的事务

这或许是造成表膨胀最常见的诱因。一个长时间运行的事务,无论处于活跃还是空闲状态,都会阻碍 VACUUM 回收该事务启动后由 UPDATE 或 DELETE 操作产生的失效元组。这是因为该长事务可能需要读取这些元组更新前的版本。

启动一个事务,并获取其事务 ID。

BEGIN;

SELECT txid_current();
 txid_current
--------------
          782
(1 row)

在另一个独立会话中,执行数据删除操作,并运行 VACUUM 清理命令。

DELETE FROM t;

VACUUM (VERBOSE) t;
INFO:  00000: vacuuming "postgres.public.t"
INFO:  00000: finished vacuuming "postgres.public.t": index scans: 0
pages: 0 removed, 1 remain, 1 scanned (100.00% of total), 0 eagerly scanned
tuples: 0 removed, 100 remain, 100 are dead but not yet removable
removable cutoff: 782, which was 2 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
visibility map: 0 pages set all-visible, 0 pages set all-frozen (0 were all-visible)
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 15 hits, 0 reads, 0 dirtied
WAL usage: 0 records, 0 full page images, 0 bytes, 0 full page image bytes, 0 buffers full
memory usage: dead item storage 0.02 MB accumulated across 0 resets (limit 64.00 MB each)
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

此时会出现提示消息tuples: 0 removed, 100 remain, 100 are dead but not yet removable,表明失效元组未被回收;而消息removable cutoff: 782, which was 2 XIDs old when operation ended则说明,事务号 782 是引发该问题的根源。

长事务产生的危险

在一个数据库连接上长时间运行的事务,对于非常活跃且频繁进行 vacuum 的表,会让表的访问速度变得非常慢,因为 vacuum 无法回收比那个长时间运行的事务更新的已删除行。

事务中更新的任何行都可能导致其他事务等待。这些事务也可能持有锁,现在它们停滞不前,这可能会导致它们阻止其他事务。此模式会形成阻塞链,并且在 OLTP 工作负载中,可能会大大降低性能。

推荐的做法也很简单:在尽可能短的时间内保持所需的锁。

列出长事务和慢查询

编写不当的查询或结构不佳的数据,可能会导致数据库的执行时间过长。通常是通过数据库响应缓慢或服务器 CPU 使用率的长时间增加来发现问题,pg_stat_activity 视图可以帮助找出导致问题的查询。pg_stat_activity 视图包含所有当前正在运行的查询的详细信息,包括用户、连接和时间相关信息。

有时,应用程序可能会在打开的事务期间卡住/停顿,并且由于进程本身没有终止,数据库连接被泄露。这将留下一个长时间运行的事务,该事务在很长一段时间内未能提交/回滚,直到当 vacuum 在非常活跃的表上变得毫无意义时,就导致了数据库系统运行缓慢。

您可以使用下面的查询,列出 PostgreSQL 中长时间运行的事务和慢查询:

SELECT datname, pid, usename, application_name,
    client_addr, backend_start, xact_start, state_change,
    waiting, query
FROM pg_stat_activity
WHERE ( now() - xact_start ) > '30 minutes'
   OR ( now() - state_change ) > '10 minutes'
ORDER BY xact_start;

或者,您也可以使用下面的查询,来查找运行时间最长的事务:

SELECT datname, pid, usename,
    state, backend_xmin, xact_start
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC;

终止长事务

如果某些查询看上去不会很快完成,则可以使用 pg_stat_activity 或 pg_locks 视图中的pid(进程 ID)来终止正在运行的进程。

  • pg_cancel_backend(pid) 将尝试取消一个后端进程正在运行的查询。
  • pg_terminate_backend(pid) 将立即终止正在运行查询的进程,但可能会对数据库服务器上运行的其他查询产生副作用。运行 pg_terminate_backend 时可能会重置全部的连接,因此其他正在运行的查询可能会受到影响。应该将其作为最后的手段使用。

要以一种主动的方式来处理长时间运行的事务,您可以:

  • statement_timeout设置为一个大的值,让慢查询自动超时,或者
  • 设置idle_in_transaction_session_timeout,让在一个打开的事务中空闲的会话超时退出,或者
  • log_min_duration_statement设置为至少记录长时间运行的查询,以便您可以对它们设置警报,和手动终止它们。

了解更多

PostgreSQL 监控