如何查找并停止在PostgreSQL上运行的查询

541 阅读8分钟

你的PostgreSQL服务器正在瘫痪,一些顽皮的查询消耗了太多的资源或者阻塞了其他的查询。 不要惊慌!你可以停止这些问题查询,稳定你的系统。

在这篇文章中,我们将看看如何通过SQL停止查询,寻找有问题的查询的技术,以及通过操作系统工具取消查询的偶尔有用的能力。

通过SQL停止查询分两步走

以下是查找和停止查询的基本过程,注意你需要以一个有足够权限的用户身份连接,比如管理员账户。

1.找到pid

PostgreSQL为每个连接创建一个进程,它用操作系统的进程ID或pid来识别每个进程。 为了取消一个查询,你需要知道它所运行的连接的pid。

找到这一点的一个方法是使用pg_stat_activity ,它提供了关于实时查询的信息。 例如,尝试这个查询。

SELECT pid, state, backend_start, substr(query, 0, 100) q
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;

substr调用将显示的SQL限制为100个字符,以避免长的查询占用大量的屏幕空间。backend_type 过滤器避免显示后台服务器进程,如autovacuum启动器。按backend_start 排序,首先显示运行时间最长的连接,这往往会显示有问题的长期运行的事务。

这是一个在我的开发服务器上运行这个的例子。

stagingi_inventev=# SELECT pid, state, backend_start, substr(query, 0, 100) q
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;
 pid | state  |         backend_start         |                                                  q
-----+--------+-------------------------------+-----------------------------------------------------------------------------------------------------
  73 | active | 2022-06-17 18:57:15.850803+00 | SELECT pid, state, backend_start, substr(query, 0, 100) q FROM pg_stat_activity WHERE backend_type
  77 | idle   | 2022-06-17 18:57:33.567574+00 | SELECT /* long-running query */ pg_sleep(10000);
(2 rows)

另一个查找pid的方法是通过操作系统,使用像htp这样的工具。 如果你能访问你的PostgreSQL服务器,并想找到消耗资源最多的查询,这很有用。

好了,你有了一个pid,现在让我们停止这个查询吧!

2.终止,或取消,进程

PostgreSQL有两个停止查询的函数,其区别在下面讨论。

苛刻的 "是 pg_terminate_backend,你可以像这样使用。

SELECT pg_terminate_backend(pid);

我默认使用这个函数,原因在下面解释。

较好的 "功能是 pg_cancel_backend,你可以像这样使用。

SELECT pg_cancel_backend(pid);

当使用这两个函数时,用你在步骤1中找到的pid替换pid 。例如,终止上面那个长期运行的查询。

SELECT pg_terminate_backend(77);

然后砰的一声,它就消失了。

有两个不同之处,使pg_terminate_backend "更严厉"。

首先,pg_terminate_backend 完全停止进程,导致连接关闭。 这将回滚连接上的任何开放事务,释放其持有的所有锁。

相比之下,pg_cancel_backend 只中断正在运行的查询,让连接保持开放。 当前的事务或保存点被中止。 因此,如果连接使用保存点,它仍然可以保持周围的事务开放,并有待定的数据变化和锁。

其次,pg_terminate_backend 是立即适用的*,而pg_cancel_backend 可以在后端进程生命周期的某些点上推迟。因此,有时你可能会运行pg_cancel_backend ,但在一段时间内什么都没有发生。 具体来说,这可能发生在进程从客户端读取输入时,比如一个传入的查询。 后端进程推迟处理取消,直到所有的输入都被读取,因为否则连接就不能保持开放和功能。

(*Ackshualllypg_terminate_backend可能无法立即应用,但这种可能性要小得多。代码的小部分也会推迟处理。理论上,这些部分不会花太多时间执行,但永远不会说永远。如果你有兴趣深入研究源码,可以从ProcessInterrupts 中开始。 src/backend/tcop/postgres.c.)

我默认使用pg_terminate_backend 。通常,当我需要停止一个查询时,我想停止启动它的整个应用进程,回滚所有的数据变化,并释放所有的锁。当使用pg_cancel_backend ,应用程序的错误处理代码有可能回滚事务/保存点,并继续运行类似的查询。 而且它可能继续持有问题锁。

发现顽固的运行查询

如果你想停止一个以上的查询,对它们逐一运行pg_terminate_backend() ,可能会很麻烦。你可以使用SQL来找到不好的查询并生成终止语句,以便轻松地摆脱它们。 这里有几个例子。

阻止特定进程的查询

如果你在执行一个ALTER TABLE ,发现它被阻塞了,在等待一个表的锁,你可能想终止持有该表锁的连接。这将允许ALTER TABLE 继续。

例如,我最近在一个应用程序上工作,有几个长期运行的事务阻塞了数据库迁移。 这些长期运行的查询终止是安全的,因为负责的应用程序进程将在以后重新运行并填补任何空白。

你可以通过对pg_stat_activity 的查询来找到ALTER TABLE 的阻塞的pid,就像这样。

SELECT pid, state, backend_start, substr(query, 0, 100) q
FROM pg_stat_activity
WHERE backend_type = 'client backend' AND query LIKE 'ALTER TABLE%'
ORDER BY backend_start;

有了阻塞的pid,你可以用这个查询与 pg_blocking_pids来生成SQL来终止阻塞的进程。

SELECT 'SELECT pg_terminate_backend(' || pid || ');'
FROM pg_stat_activity
WHERE pid = ANY (pg_blocking_pids(blockedpid));

要运行这个查询,用被阻塞的进程的pid替换blockedpid 。 然后复制粘贴输出行并运行它们。

下面是一个使用这些查询来解封ALTER TABLE 的实例会话。

mydb=# SELECT pid, state, backend_start, substr(query, 0, 100) q
  FROM pg_stat_activity
  WHERE backend_type = 'client backend' AND query LIKE 'ALTER TABLE%'
  ORDER BY backend_start;
 pid | state  |         backend_start         |                       q
-----+--------+-------------------------------+------------------------------------------------
 613 | active | 2022-06-17 20:25:06.604326+00 | ALTER TABLE auth_user ADD COLUMN age int null;
(1 row)

mydb=# SELECT 'SELECT pg_terminate_backend(' || pid || ');'
  FROM pg_stat_activity
  WHERE pid = ANY (pg_blocking_pids(613));
             ?column?
----------------------------------
 SELECT pg_terminate_backend(77);
(1 row)

mydb=# SELECT pg_terminate_backend(77);
 pg_terminate_backend
----------------------
 t
(1 row)

mydb=# SELECT pid, state, backend_start, substr(query, 0, 100) q
  FROM pg_stat_activity
  WHERE backend_type = 'client backend' AND query LIKE 'ALTER TABLE%'
  ORDER BY backend_start;
 pid | state |         backend_start         |                       q
-----+-------+-------------------------------+------------------------------------------------
 613 | idle  | 2022-06-17 20:25:06.604326+00 | ALTER TABLE auth_user ADD COLUMN age int null;
(1 row)

Cowabunga!

对某一特定表的查询

有时,你可能只想终止所有针对某个表的查询,这对于阻止某个特定的行为不端的应用进程的过载是合适的。

这个查询将生成SQL,以终止所有运行中的查询,这些查询看起来是在使用一个名为auth_user 的特定表。

SELECT 'SELECT pg_terminate_backend(' || pid || ');'
FROM pg_stat_activity
WHERE query LIKE '%auth_user%'
  AND pid != pg_backend_pid();

要使用这个查询,请在运行前改变LIKE '%auth_user%' 中的匹配表名。然后复制粘贴输出行并运行。

queryLIKE 匹配是有点直白的,因为它有可能出现假阳性,但它很简单。 与 pg_backend_pid的比较是为了避免匹配当前的连接。

下面是一个使用这个查询来终止对一个叫做library_book 的表的所有查询的例子。

mydb=# SELECT 'SELECT pg_terminate_backend(' || pid || ');'
  FROM pg_stat_activity
  WHERE query LIKE '%library_book%'
    AND pid != pg_backend_pid();
           ?column?
-------------------------------
 SELECT pg_terminate_backend(123);
 SELECT pg_terminate_backend(124);
(1 row)

mydb=# SELECT pg_terminate_backend(123);
SELECT pg_terminate_backend(124);
 pg_terminate_backend
-------------------
 t
(1 row)

 pg_terminate_backend
-------------------
 t
(1 row)

🔥🔥🔥

打开时间超过N秒的连接

最后一个例子:如何过滤掉那些开放时间超过N秒的连接。 这是很钝的锤子,但你可以在紧急情况下试试。

SELECT 'SELECT pg_terminate_backend(' || pid || ');'
FROM pg_stat_activity
WHERE backend_type = 'client backend'
      AND pid != pg_backend_pid()
      AND backend_start < NOW() - '10 seconds'::interval;

适当地调整'10 seconds'

比如说:

mydb=# SELECT 'SELECT pg_terminate_backend(' || pid || ');'
FROM pg_stat_activity
WHERE backend_type = 'client backend'
      AND backend_start < now() - '10 seconds'::interval
      AND pid != pg_backend_pid();
              ?column?
------------------------------------
 SELECT pg_terminate_backend(2675);
 SELECT pg_terminate_backend(2676);
(2 rows)

mydb=# SELECT pg_terminate_backend(2675);
SELECT pg_terminate_backend(2676);
 pg_terminate_backend
----------------------
 t
(1 row)

WARNING:  PID 2676 is not a PostgreSQL server process
 pg_terminate_backend
----------------------
 f
(1 row)

😅 注意在这种情况下,似乎pid 2676在生成SQL和运行SQL之间完成。

通过操作系统停止查询

如果你能访问你的PostgreSQL服务器,你也可以通过给相关的操作系统进程发出信号来停止查询。 这需要你有对PostgreSQL服务器的shell访问权,现在随着管理数据库平台的兴起,这种情况并不常见。

你可以使用像htop这样的工具来检查正在运行的后端进程,你可以根据 CPU 或内存的高使用率来找到有问题的进程。

后台进程作为PostgreSQL服务器主进程的子进程出现,注意你要选择正确的pid,因为如果杀死主进程,整个PostgreSQL服务器会关闭。

在Linux/MacOS/Unixes上

在Unix操作系统上,你可以通过发送其进程SIGTERM终止信号)来终止一个正在运行的查询。 kill:

$ kill -SIGTERM pid

用后端进程的pid替换pid

而你可以通过发送SIGINT中断信号)来取消一个进程。

$ kill -SIGINT pid

在Windows上

Windows 没有kill 命令,所以 PostgreSQL 提供了pg_ctl 工具来向进程发送信号。你可以用它来终止一个查询,像这样发送终止信号。

$ pg_ctl kill TERM pid

用目标后端pid替换pid

你可以用它通过发送中断信号来取消一个查询,就像这样。

$ pg_ctl kill INT pid

...bye bye query!

杀死查询进程

使用操作系统,我们也可以通过 "杀死 "一个进程来保证立即停止它。 这就是 "杀死 "工具名字的由来。 (以及它过时的、暴力的术语。)在一个进程在终止后继续消耗资源的罕见情况下,这样做很有用,这可能是由于PostgreSQL的一个错误。

然而,这个动作是非常激烈的。 立即终止会立即停止进程,没有任何机会进行清理。 PostgreSQL的设计应该防止数据丢失(已提交的行),但你可能会错过其他偶然的数据,如日志信息。

要杀死一个进程,请向它发送KILL 信号。

在 Unixes 上:

$ kill -KILL pid

在Windows上:

$ pg_ctl kill KILL pid

💥 Kaboom!

Fin

愿你很少需要使用这些知识。