在日常的工作中,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 也无济于事。
此时,我们看看通过其他的命令是否可以将这个进程或者这个语句杀死。
1 先找到这个语句的PID
2 然后不断的针对这个PID 进行 pg_terminate_backend 的操作,但可以才能够下图看到,怎么操作,系统均无法响应这个操作。
然后我们直接针对这个进程,进行DEBUG
可以从图中发现,主要的原因是调用了/lib64/libc.so.6 的这个系统函数导致的问题。
那么此时如何将这个进程KILL 掉,别无他法,只能在数据库外部对这个进程进行KILL -9 的操作。当然这个操作是具有危险性的,所以必须注意相关的操作的时间和相关数据库应该在操作前进行备份,甚至停止业务。
而这个问题根本的解决方案在于在外部函数调用的过程中,必须在编写外部函数时在循环中加入对于 CHECK_FOR_INTERRUPTS()函数的调用,来解决这个问题。
所以此时你还敢随便使用没有被经过验证的外部C语言函数在POSTGRESQL 内部的使用,以及一些来路不明的 extension 吗?