PostgreSQL 无法停止的SQL是怎么形成的与 复现这个问题

这篇博客探讨了在PostgreSQL中如何中断运行中的SQL语句,包括使用PQgetcancel()和PQcancel()函数,以及SIGINT和SIGTERM信号。当SQL陷入死循环或外部函数调用时,常规中断方法可能无效。作者通过示例展示了即使发送pg_terminate_backend()也无法终止进程的情况,并指出可能的原因,如系统调用阻塞。解决方案是在循环中添加对CHECK_FOR_INTERRUPTS()的调用。博客最后提醒用户谨慎使用未经验证的外部C语言函数和扩展。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

64349ac0813421297ec032babef907e1.png

在日常的工作中,DB人员会遇到不少的关于停止某个,“有意思” 的SQL的问题,这些SQL 可能正在BLOCKED 掉其他的语句,或者写这个语句的人,正在无意的摧毁整个数据库系统。而DBA只能采用PG的方式 terminal or cancal 这样的语句。

通过两个命令来完成这个任务是PGDB的必须的知识,可如果更细致的问及,内部是怎么进行工作的,这个估计的 take a long story.

Postgresql协议提供了中断运行语句的功能,这个功能是通过打开一个新的连接并且发送一个加密的cancle request  命令来完成的,这个加密的key通过原有的连接产生并转移过来的。主要的原因是如果不这样,每个人都可以cancel你的查询,这样就没有任何的安全可言了。    

libpg 提供了两个函数PQgetcancel () 和 PQcancel 两个函数来获得语句句柄和将其cancel 掉,而在POSTGRESQL中如果你在允许的语句环境中直接使用ctrl + c 会直接启动函数将你的语句被 cancel掉。 

当然postmaster进程接受到你的cancelRequest 的需求,他会发送SIGINT到你的backend所在的数据库的会话,同理如果你触发了 pg_cancel_backend()则也会触发同样的SIGINT 到对应的数据库会话中,而如果你使用了pg_terminate_backend() 函数则会发送信号SIGTERM到对应的会话中。

当进程接受到SIGINT会发送QueryCancelPending 的信号,同理当进程接受到 SIGTERM 的信号会发送ProcDiePending信号,也就是说当系统接受到取消SQL的指令后并不是马上将SQL 停止掉,而是开始设置全局变量来针对这个执行SQL的进程。

设置的主要的原因也很简单,一个SQL在运行中,会改变数据的状态,尤其是内存数据的状态,而改变状态后,将其KILL,则到底内存数据的状态如何变化,这必须被控制和检测,所以设置全局变量的主要思考的地方在于,针对SQL修改的数据是否需要还原进行界定并在系统安全的情况下,才能对SQL所在的进程进行KILL。

CHECK_FOR_INTERRUPTS()宏调用ProcessInterrupts()函数,这些调用散布在PostgreSQL代码中所有安全的地方。然后,该函数将抛出取消当前语句的错误,或者将终止后端进程,这取决于设置了哪个标志。

那么回到主题,为什么你KILL了进程的SQL ,但是却没有效果

1  在执行的过程中,无法调用CHECK_FOR_INTERRUPTS, 并且陷入死循环,在这样的情况下将无法对SQL进行查杀。

2  非POSTGRESQL 中的函数(C 系列)正在被这个函数调用,所以你没有办法调用CHECK_FOR_INTERRUPTS 来终止这个语句。

3  发送的SINGAL 信号卡在系统调用的阶段,这个问题可能来自于操作系统或者硬件系统层级。

我们怎么来模拟这个问题,首先我们建立一个基于C语言的死循环的函数,通过gcc 来编译并且将其加载到POSTGRESQL 系统中的libs 中,注意这里面的权限问题和文件的可执行问题。

然后将这个文件加载到POSTGRESQL 系统中,(因为安全问题,相关的语句和编译方法暂不提供)

下面在POSTGRESQL 系统中直接调用这个C 语言撰写的函数,此时系统在这个backend 进入了死循环,如下图及时在本session中持续不断的按住 ctrl + c 也无济于事。

2359a6e0b80e5e255dec776a72b49937.png

此时,我们看看通过其他的命令是否可以将这个进程或者这个语句杀死。

1 先找到这个语句的PID 

801d061bf4e91327074d7f7a9d09ca36.png

2  然后不断的针对这个PID 进行 pg_terminate_backend 的操作,但可以才能够下图看到,怎么操作,系统均无法响应这个操作。

f986cdf162221fe13740a0918cba17fb.png

然后我们直接针对这个进程,进行DEBUG

181c5e2aa238adcc4ef1e2d072fd0b73.png

977edbc8044dfa31a9adc42abc01b4ba.png

可以从图中发现,主要的原因是调用了/lib64/libc.so.6 的这个系统函数导致的问题。

那么此时如何将这个进程KILL 掉,别无他法,只能在数据库外部对这个进程进行KILL -9 的操作。当然这个操作是具有危险性的,所以必须注意相关的操作的时间和相关数据库应该在操作前进行备份,甚至停止业务。

而这个问题根本的解决方案在于在外部函数调用的过程中,必须在编写外部函数时在循环中加入对于 CHECK_FOR_INTERRUPTS()函数的调用,来解决这个问题。

所以此时你还敢随便使用没有被经过验证的外部C语言函数在POSTGRESQL 内部的使用,以及一些来路不明的 extension 吗?

be22567afccaa7740ed78c21f2fe02be.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值