IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Postgresql中如何终止正在执行的查询 -> 正文阅读

[大数据]Postgresql中如何终止正在执行的查询

在使用数据库过程中,我们难免要终止一些正在执行的查询等语句,比如不合理的超长大事物,对数据库性能有影响的偶发性查询。一般在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 SIGLOST         29
*/
#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宏定义如下:

/* Service interrupt, if one is pending and it's safe to service it now */
#define CHECK_FOR_INTERRUPTS() \
do { \
	if (INTERRUPTS_PENDING_CONDITION()) \
		ProcessInterrupts(); \
} while(0)

发出终止或者取消的命令后,有时候为什么没效果呢?

  1. 发出的命令被卡在不包含CHECK_FOR_INTERRUPTS()的循环中。
  2. 在SQL语句中调用的第三方C函数的执行被卡住了。在这种情况下,可以将错误报告给函数的作者。
  3. 在无法中断的系统调用中被卡住。这可能是操作系统或硬件层面的问题。注意,当进程处于内核空间中时,信号的传递会被延迟。
如果终止或取消的命令没效果,有些人可能会使用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()

#使用任意方式终止取消该查询
#在psql下ctrl+c无用,如下
postgres=# select loop();
^CCancel request sent

#另外开启一个session,查询pg_stat_activity,如下
postgres=# SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
  pid  |                               query                                
-------+--------------------------------------------------------------------
 31449 | select loop();
 31853 | SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';

#使用终止函数,一样无效
postgres=# SELECT pg_terminate_backend(31449);
 pg_terminate_backend 
----------------------
 t
(1 row)

#该函数还在执行
postgres=# SELECT pid, query FROM pg_stat_activity WHERE query LIKE '%loop%';
  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
#0  0x00007ff0535c58d0 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x00007ff0535c5784 in sleep () from /lib64/libc.so.6
#2  0x00007ff03509f725 in loop () from /opt/pgsql13/lib/loop.so
#3  0x0000000000619db2 in ExecEvalFuncExprFusage (state=state@entry=0x15127f8, op=op@entry=0x1512a08, econtext=econtext@entry=0x1512520)
    at execExprInterp.c:2341
#4  0x000000000061c88e in ExecInterpExpr (state=0x15127f8, econtext=0x1512520, isnull=<optimized out>) at execExprInterp.c:715
#5  0x000000000064c5ff in ExecEvalExprSwitchContext (isNull=0x7ffc3a795f6f, econtext=0x1512520, state=0x15127f8)
    at ../../../src/include/executor/executor.h:322
#6  ExecProject (projInfo=0x15127f0) at ../../../src/include/executor/executor.h:356
#7  ExecResult (pstate=<optimized out>) at nodeResult.c:136
#8  0x0000000000620d12 in ExecProcNode (node=0x1512410) at ../../../src/include/executor/executor.h:248
#9  ExecutePlan (execute_once=<optimized out>, dest=0x1514838, direction=<optimized out>, numberTuples=0, sendTuples=true, operation=CMD_SELECT, 
    use_parallel_mode=<optimized out>, planstate=0x1512410, estate=0x15121e8) at execMain.c:1632
#10 standard_ExecutorRun (queryDesc=0x14f88e8, direction=<optimized out>, count=0, execute_once=<optimized out>) at execMain.c:350
#11 0x000000000077b25b in PortalRunSelect (portal=portal@entry=0x1498c08, forward=forward@entry=true, count=0, count@entry=9223372036854775807, 
    dest=dest@entry=0x1514838) at pquery.c:921
#12 0x000000000077c678 in PortalRun (portal=portal@entry=0x1498c08, count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, 
    run_once=run_once@entry=true, dest=dest@entry=0x1514838, altdest=altdest@entry=0x1514838, qc=qc@entry=0x7ffc3a7961e0) at pquery.c:765
#13 0x000000000077835e in exec_simple_query (query_string=0x13fcf78 "select loop();") at postgres.c:1239
#14 0x00000000007796d2 in PostgresMain (argc=<optimized out>, argv=argv@entry=0x1427a78, dbname=0x14279b8 "postgres", username=<optimized out>)
    at postgres.c:4337
#15 0x000000000048650c in BackendRun (port=<optimized out>, port=<optimized out>) at postmaster.c:4550
#16 BackendStartup (port=0x141f650) at postmaster.c:4234
#17 ServerLoop () at postmaster.c:1739
#18 0x0000000000706b88 in PostmasterMain (argc=argc@entry=3, argv=argv@entry=0x13f7b90) at postmaster.c:1412
#19 0x000000000048718a in main (argc=3, argv=0x13f7b90) at main.c:210

#由以上堆栈我们看到loop () from /opt/pgsql13/lib/loop.so和sleep () from /lib64/libc.so.6,
#而且也设置了ProcDiePending,所以我们只需要调用ProcessInterrupts()即可退出正在执行的loop。
(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:

#已被终止,并重置了session
postgres=# select loop();
^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=# 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%';
修改代码,做一个可以中断取消的例子:
-- 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)
{
    /* 加入CHECK_FOR_INTERRUPTS,每两秒检查一次中断,以便可以手动终止 */
    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/

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-08 19:06:49  更:2022-06-08 19:09:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 0:33:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码