PgSQL技术内幕 • statement_timeout做的那些事
statement_timeout是Postgres种的一个配置参数,用于指定SQL语句执行的超时时间,当超时时就取消该SQL的执行,并返回错误信息。这个参数通常用于控制运行时间较长的查询,避免影响数据库性能和响应时间。一旦一条SQL查询花费几分钟甚至更长时间才能执行完时,若没有限制,这种查询可能占用数据库资源,导致其他请求阻塞。
statement_timeout默认值是0,表示不限制SQL查询执行时间,单位为毫秒。那么,这个参数是如何做到控制SQL的执行时间的呢?
1、statement_timeout超时的起使时间点在哪?
配置项在代码中对应的变量为StatementTimeout。
事务开启时,StartTransactionCommand之后的时间点作为超时时间计时开始,即上图中蓝色框内的now值。超时时间点:now + statement_timeout的值作为fin_time记录到计时器中。
2、超时机制
上节,我们知道开始事务的时间点作为超时的起使计时点,通过schedule_alarm设置定时器。那么这个定时器通过什么来触发?
1)我们接着看下schedule_alarm函数,其中enable_alarm函数将alarm_enabled置为true,后续会用到,然后通过settimer函数设置一个定时器,也就是超时后会向进程发送一个SIGALRM信号:
2)SIGALRM信号接收到后,做什么动作呢?接着观察SIGALRM信号注册函数句柄:
PostgresMain作为PG服务进程的入口函数,在InitializeTimeouts函数初始化all_timeouts[]数组时,注册SIGALRM信号句柄函数,即handle_sig_alarm。
在1)中设置了alarm_enabled为true,handle_sig_alarm进入超时处理流程,即处理active_timeouts[]数组每个超时事件(拿一个删除一个,所以总是取active_timeouts[0]):标记indicator=true,并调用超时句柄,针对statement_timeout,在InitPostgres->RegisterTimeout函数设置了句柄为StatementTimeoutHandler。
StatementTimeoutHandler通过kill向进程发送SIGINT信号。
3)PostgresMain也注册了SIGINT信号处理函数:StatementCancelHandler:
主要设置了两个变量:InterruptPending和QueryCancelPending都为true。
4)进入中断处理函数ProcessInterrupts后,根据QueryCancelPending为true,进入取消SQL执行的逻辑:通过ereport::ERROR 跳出当前流程到异常结束:
3、ProcessInterrupts何时被执行
很显然,statement_timeout是通过一系列软中断来完成的。当发生中断的时候,CPU会停下当前处理流程,进入内核态进行中断信号处理,中断信号处理完,返回用户态时处理注册的中断处理函数,然后返回中断处理前软件流程接着工作。所以ProcessInterrupts什么时候被执行呢?
CHECK_FOR_INTERRUPTS->ProcessInterrupts:InterruptPending在函数StatementCancelHandler中就置为了true,所以CHECK_FOR_INTERRUPTS宏会调用到ProcessInterrupts。而PG关键流程中有很多地方都会调用CHECK_FOR_INTERRUPTS来检测中断的发生并处理。
一旦PG流程陷入某些底层函数出不来,导致statement_timeout超时,就会因为不能继续执行后续流程进入CHECK_FOR_INTERRUPTS做真正取消SQL的操作。也就是说这种情况下,statement_timeout是管不住的!