在使用数据库过程中,我们难免要终止一些正在执行的查询等语句,比如不合理的超长大事物,对数据库性能有影响的偶发性查询。一般在pg中使用以下两个函数终止相关查询,这里不在详述两个函数的区别。
直接给出官方文档解释:
pg_cancel_backend 调用系统信号 SIGINT 对应信号2 pg_terminate_backend 调用系统信号 SIGTERM 对应信号15
--定义在/usr/include/asm/signal.h
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
或者kill -l也可以查看
没安装man-pages的话,执行man 7 signal会报错No manual entry for signal in section 7 安装man-pages
yum install -y man-pages
看下POSIX.1-1990标准中描述的信号 postgresql的pg_ctl可以通过kill对相应进程发送以下几种信号
pg_ctl kill SIGNALNAME pid
SIGNALNAME可以是 ABRT HUP INT KILL QUIT TERM USR1 USR2中的一个
举例: 查找到postgres主进程号为4209 发送INT信号给4209进行 有了以上的介绍,我们描述下postgresql中是如何取消查询等语句的,PostgreSQL协议对中断正在运行的语句是有一定准则的。它通过打开一个新连接并发送一个带有密钥的CancelRequest消息来完成的。在初始连接开始期间,服务端会发送该密钥。如果没有这个密钥,每个人都可以取消并终止我们的查询,这是一个不可接受的安全问题。
C库中的libpq提供了PQgetCancel()和PQcancel()函数来取消查询,其他数据库api也应该有类似的函数。在交互式psql会话中,我们可以简单地按Ctrl+C发送取消请求,这个操作我们应该会经常用到,GUI客户端通常有一个用于取消终止的按钮。
数据库服务如何处理取消请求
PostgreSQL进程间通信很大程度上依赖于信号。 postmaster进程接收到CancelRequest时,它向相应数据库会话的后端进程发送SIGINT信号。该信号是由函数pg_cancel_backend()发送。pg_terminate_backend()发送信号SIGTERM。文章最开始有说明。
每个PostgreSQL进程都有一个信号处理器,当接收到信号时,会对这些信号进行处理。这个信号处理器不会立即中断后端进程,但它会为该进程设置全局变量。SIGINT将设置QueryCancelPending, SIGTERM将设置ProcDiePending。这些变量作为标志,并在适当的时候,由后端进程负责对它们做出反应。这确保了进程不会在不适宜的时候被中断,例如,当该进程使共享内存处于不一致的状态的时候。
通过CHECK_FOR_INTERRUPTS()宏调用ProcessInterrupts()函数,这些调用分布在PostgreSQL代码中很多地方。然后,该函数将抛出是取消当前语句的错误,还是终止后端进程的错误,这取决于之前设置的变量标志(SIGINT将设置QueryCancelPending, SIGTERM将设置ProcDiePending)。
CHECK_FOR_INTERRUPTS宏定义如下:
#define CHECK_FOR_INTERRUPTS() \
do { \
if (INTERRUPTS_PENDING_CONDITION()) \
ProcessInterrupts(); \
} while(0)
发出终止或者取消的命令后,有时候为什么没效果呢?
- 发出的命令被卡在不包含CHECK_FOR_INTERRUPTS()的循环中。
- 在SQL语句中调用的第三方C函数的执行被卡住了。在这种情况下,可以将错误报告给函数的作者。
- 在无法中断的系统调用中被卡住。这可能是操作系统或硬件层面的问题。注意,当进程处于内核空间中时,信号的传递会被延迟。
如果终止或取消的命令没效果,有些人可能会使用kill -9 pid,那么在postgresql中这会带来什么后果呢?
在PostgreSQL后台进程上使用普通的kill是完全没问题的。这将发送SIGTERM信号,它与调用pg_terminate_backend()是相同的。如果这都没有效果,那么很容易使用kill -9,它会发送SIGKILL。该信号无法被捕获并立即终止进程。问题是postmaster如果探测到一个子进程没有干净地关闭。那么,它将杀死所有其他PostgreSQL进程,并进行崩溃恢复,这将导致整个数据库中断,可能需要几秒到几分钟的时间。如果数据库很大并且很繁忙的话,可能需要更久。
注意,虽然在普通的后端进程使用kill -9会导致短时间的停机,但在postmaster进程上使用kill -9会造成更恶劣的影响,因此要避免在postmaster上使用kill -9。它打开了一个时间窗口,在此期间可以启动一个新的postmaster,而旧的postmaster的一些子进程还存活着,这很可能导致磁盘上的数据损坏。所以,永远,永远不要用kill -9杀死postmaster进程!
有时候,kill -9也不能杀死PostgreSQL后台进程。这意味着后端被卡在一个不可中断的系统调用中,例如在不再可用的网络存储上有I/O操作。如果这种情况一直持续,那么摆脱该进程的唯一方法就是重启操作系统。
下面我们模拟一个无法终止查询的例子:
创建一个c函数:
--loop.c文件内容如下
#include "postgres.h"
#include "fmgr.h"
#include <unistd.h>
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(loop);
Datum loop(PG_FUNCTION_ARGS)
{
while(1)
sleep(2);
}
-- 创建共享库文件
gcc -I /opt/pgsql13/include/server/ -fPIC -shared -o loop.so loop.c
-- 查看pg的libdir,拷贝共享库文件到该目录
pg_config --libdir
/opt/pgsql13/lib
mv loop.so /opt/pgsql13/lib
进入数据库,使用共享库文件创建loop函数,然后调用
CREATE OR REPLACE FUNCTION loop() RETURNS void as
'loop.so', 'loop'
LANGUAGE c ;
select loop()
postgres=
^CCancel request sent
postgres=
pid | query
31449 | select loop();
31853 | SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
postgres=
pg_terminate_backend
t
(1 row)
postgres=
pid | query
31449 | select loop();
31853 | SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
通过gdb调试一下
gdb /opt/pgsql13/bin/postgres 31449
(gdb) bt
at execExprInterp.c:2341
at ../../../src/include/executor/executor.h:322
use_parallel_mode=<optimized out>, planstate=0x1512410, estate=0x15121e8) at execMain.c:1632
dest=dest@entry=0x1514838) at pquery.c:921
run_once=run_once@entry=true, dest=dest@entry=0x1514838, altdest=altdest@entry=0x1514838, qc=qc@entry=0x7ffc3a7961e0) at pquery.c:765
at postgres.c:4337
(gdb) print ProcessInterrupts()
[Inferior 1 (process 31449) exited with code 01]
The program being debugged exited while in a function called from GDB.
Evaluation of the expression containing the function
(ProcessInterrupts) will be abandoned.
再次查看原来的session:
postgres=
^CCancel request sent
FATAL: terminating connection due to administrator command
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Succeeded.
postgres=
pid | query
31853 | SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
修改代码,做一个可以中断取消的例子:
-- loop_01.c内容如下
#include "postgres.h"
#include "fmgr.h"
#include "miscadmin.h"
#include <unistd.h>
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(loop);
Datum loop(PG_FUNCTION_ARGS)
{
while(1)
{
CHECK_FOR_INTERRUPTS();
sleep(2);
}
}
-- 方便起见,共享库文件还是用loop.so
gcc -I /opt/pgsql13/include/server/ -fPIC -shared -o loop.so loop_01.c
mv /home/postgres/loop.so /opt/pgsql13/lib/
--psql中可以取消终止
postgres=# select loop();
^CCancel request sent
ERROR: canceling statement due to user request
--pg_terminate_backend也可以正常终止
postgres=# SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
pid | query
-------+--------------------------------------------------------------------
322 | select loop();
31853 | SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
(2 rows)
postgres=# SELECT pg_terminate_backend(322);
pg_terminate_backend
----------------------
t
(1 row)
postgres=# SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
pid | query
-------+--------------------------------------------------------------------
31853 | SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
(1 row)
通过以上实例, 我们知道取消查询就是向后端发送一个SIGINT信号。如果SIGINT和SIGTERM都不能中断后端进程,我们可以用gdb连接到挂起的后端进程,并直接调用ProcessInterrupts()使其退出。
参考: https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.9 https://postgreshelp.com/operating-system-kill-signals-on-postgresql/ https://www.cybertec-postgresql.com/en/cancel-hanging-postgresql-query/
|