你的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 ,但在一段时间内什么都没有发生。 具体来说,这可能发生在进程从客户端读取输入时,比如一个传入的查询。 后端进程推迟处理取消,直到所有的输入都被读取,因为否则连接就不能保持开放和功能。
(*Ackshuallly,pg_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%' 中的匹配表名。然后复制粘贴输出行并运行。
将query 与LIKE 匹配是有点直白的,因为它有可能出现假阳性,但它很简单。 与 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
愿你很少需要使用这些知识。