功能实现背景说明

我们已经有两个参数来控制长事务:statement_timeoutidle_in_transaction_session_timeout。但是,如果事务执行的命令足够短且不超过 statement_timeout,并且命令之间的暂停时间适合 idle_in_transaction_session_timeout,则事务可以无限期持续。

在这种情况下,transaction_timeout 可确保事务的持续时间不超过指定的超时时间。如果超过,事务和执行该事务的会话将被终止。如下:

postgres=# select version();
version
------------------------------------------------------------------------------------------------------------
PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row) postgres=# show statement_timeout ;
statement_timeout
-------------------
0
(1 row) postgres=# show transaction_timeout ;
transaction_timeout
---------------------
0
(1 row) postgres=# set transaction_timeout = '10s';
SET
postgres=# begin ;
BEGIN
postgres=*# select pg_sleep(2);
pg_sleep
---------- (1 row) postgres=*# 2024-09-28 19:28:30.891 PDT [45558] FATAL: terminating connection due to transaction timeout
postgres=*#

然后看一下进程相关,如下:

如上,45875的进程死了,会话已断开。然后随着我在psql上继续执行,又有了一个新的会话得以建立。这个问题我后面再详细解释(大家也可以看一下下图先思考思考):


我第一次看到这个的时候有点懵,原因如下:

我当时以为就像statement_timeout这样,事务超时也没必要直接断开连接 事务失败(rollback即可)。

然后带着这个疑惑,去看了一下邮件列表 如下:

注1:有兴趣的小伙伴可以自行查看邮件列表

注2:接下来我们一起看一下transaction_timeout的内部实现,以及为什么不能像statement_timeout这样去实现


功能实现源码解析

首先看一下官方文档的解释,如下:

终止事务中持续时间超过指定时间的任何会话。此限制既适用于显式事务(以 BEGIN 启动),也适用于与单个语句相对应的隐式启动事务。

如果指定此值时没有单位,则以毫秒为单位。零值(默认值)将禁用超时。

如果 transaction_timeout 短于或等于 idle_in_transaction_session_timeout 或 statement_timeout,则忽略较长的超时。

不建议在 postgresql.conf 中设置 transaction_timeout,因为它会影响所有会话。

该GUC参数定义,如下:

// src/backend/utils/misc/guc_tables.c

	{
{"transaction_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the maximum allowed duration of any transaction within a session (not a prepared transaction)."),
gettext_noop("A value of 0 turns off the timeout."),
GUC_UNIT_MS
},
&TransactionTimeout,
0, 0, INT_MAX,
NULL, assign_transaction_timeout, NULL
},

与 idle_in_transaction_session_timeout

// src/backend/tcop/postgres.c

		...
/*
* (1) If we've reached idle state, tell the frontend we're ready for
* a new query.
*
* Note: this includes fflush()'ing the last of the prior output.
*
* This is also a good time to flush out collected statistics to the
* cumulative stats system, and to update the PS stats display. We
* avoid doing those every time through the message loop because it'd
* slow down processing of batched messages, and because we don't want
* to report uncommitted updates (that confuses autovacuum). The
* notification processor wants a call too, if we are not in a
* transaction block.
*
* Also, if an idle timeout is enabled, start the timer for that.
*/
if (send_ready_for_query)
{
if (IsAbortedTransactionBlockState())
{
set_ps_display("idle in transaction (aborted)");
pgstat_report_activity(STATE_IDLEINTRANSACTION_ABORTED, NULL); /* Start the idle-in-transaction timer */
if (IdleInTransactionSessionTimeout > 0
&& (IdleInTransactionSessionTimeout < TransactionTimeout || TransactionTimeout == 0))
{
idle_in_transaction_timeout_enabled = true;
enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IdleInTransactionSessionTimeout);
}
}
else if (IsTransactionOrTransactionBlock())
{
set_ps_display("idle in transaction");
pgstat_report_activity(STATE_IDLEINTRANSACTION, NULL); /* Start the idle-in-transaction timer */
if (IdleInTransactionSessionTimeout > 0
&& (IdleInTransactionSessionTimeout < TransactionTimeout || TransactionTimeout == 0))
{
idle_in_transaction_timeout_enabled = true;
enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IdleInTransactionSessionTimeout);
}
}
...

与 statement_timeout

/*
* Start statement timeout timer, if enabled.
*
* If there's already a timeout running, don't restart the timer. That
* enables compromises between accuracy of timeouts and cost of starting a
* timeout.
*/
static void
enable_statement_timeout(void)
{
/* must be within an xact */
Assert(xact_started); if (StatementTimeout > 0
&& (StatementTimeout < TransactionTimeout || TransactionTimeout == 0))
{
if (!get_timeout_active(STATEMENT_TIMEOUT))
enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);
}
else
{
if (get_timeout_active(STATEMENT_TIMEOUT))
disable_timeout(STATEMENT_TIMEOUT, false);
}
}

如上,当transaction_timeout 小于或等于 idle_in_transaction_session_timeoutstatement_timeout,则忽略较长的超时。


transaction_timeout

// src/backend/utils/init/postinit.c

void
InitPostgres(const char *in_dbname, Oid dboid,
const char *username, Oid useroid,
bits32 flags,
char *out_dbname)
{
...
if (!bootstrap)
{
RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert);
RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IdleInTransactionSessionTimeoutHandler);
RegisterTimeout(TRANSACTION_TIMEOUT, TransactionTimeoutHandler); // here
RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler);
RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler);
RegisterTimeout(IDLE_STATS_UPDATE_TIMEOUT,
IdleStatsUpdateTimeoutHandler);
}
...
} static void
TransactionTimeoutHandler(void)
{
TransactionTimeoutPending = true;
InterruptPending = true;
SetLatch(MyLatch);
}

接下来,这里修改源码 使用ShowTransactionState函数进行打印,如下:

[postgres@localhost:~/test/bin]$ ./psql
INFO: CommitTransaction(1) name: unnamed; blockState: STARTED; state: INPROGRESS, xid/subid/cid: 0/1/0
psql (18devel)
Type "help" for help. postgres=# set transaction_timeout = '10s';
INFO: StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS, xid/subid/cid: 0/1/0
INFO: CommitTransaction(1) name: unnamed; blockState: STARTED; state: INPROGRESS, xid/subid/cid: 0/1/0
SET
postgres=# begin;
INFO: StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS, xid/subid/cid: 0/1/0
BEGIN
postgres=*# commit;
INFO: CommitTransaction(1) name: unnamed; blockState: END; state: INPROGRESS, xid/subid/cid: 0/1/0
COMMIT
postgres=# begin;
INFO: StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS, xid/subid/cid: 0/1/0
BEGIN
postgres=*# select pg_sleep(20);
2024-09-28 20:42:58.376 PDT [62092] FATAL: terminating connection due to transaction timeout
2024-09-28 20:42:58.376 PDT [62092] STATEMENT: select pg_sleep(20);
FATAL: terminating connection due to transaction timeout
server closed the connection unexpectedly1
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: INFO: CommitTransaction(1) name: unnamed; blockState: STARTED; state: INPROGRESS, xid/subid/cid: 0/1/0
Succeeded.
postgres=#

若是有小伙伴对父子事务有限状态机感兴趣的,可以查看本人之前的博客,如下:

transaction_timeout的超时启用/禁用,如下:

// src/backend/access/transam/xact.c

/*
* StartTransaction
*/
static void
StartTransaction(void)
{
...
/* Schedule transaction timeout */
if (TransactionTimeout > 0)
enable_timeout_after(TRANSACTION_TIMEOUT, TransactionTimeout);
...
}
static void
CommitTransaction(void)
{
...
/* Disable transaction timeout */
if (TransactionTimeout > 0)
disable_timeout(TRANSACTION_TIMEOUT, false);
...
} static void
PrepareTransaction(void)
{
...
/* Disable transaction timeout */
if (TransactionTimeout > 0)
disable_timeout(TRANSACTION_TIMEOUT, false);
...
} static void
AbortTransaction(void)
{
...
/* Disable transaction timeout */
if (TransactionTimeout > 0)
disable_timeout(TRANSACTION_TIMEOUT, false);
...
}

接下来我们调试一下transaction_timeout的相关内容,首先看一下enable_timeout_after的设置 如下:

注意这两个时间值,以及下面的核心设置:

其中第一个参数:ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。若是对该函数感兴趣的小伙伴可以看一下这位老哥的博客,我们这里不再赘述:


继续:

此时的函数堆栈,如下:

TransactionTimeoutHandler()
handle_sig_alarm(int postgres_signal_arg)
wrapper_handler(int postgres_signal_arg)
libpthread.so.0!<signal handler called> (未知源:0)
libc.so.6!epoll_wait (未知源:0)
WaitEventSetWaitBlock(WaitEventSet * set, int cur_timeout, WaitEvent * occurred_events, int nevents)
WaitEventSetWait(WaitEventSet * set, long timeout, WaitEvent * occurred_events, int nevents, uint32 wait_event_info)
secure_read(Port * port, void * ptr, size_t len)
pq_recvbuf()
pq_getbyte()
SocketBackend(StringInfo inBuf)
ReadCommand(StringInfo inBuf)
PostgresMain(const char * dbname, const char * username)
BackendMain(char * startup_data, size_t startup_data_len)
postmaster_child_launch(BackendType child_type, char * startup_data, size_t startup_data_len, ClientSocket * client_sock)
BackendStartup(ClientSocket * client_sock)
ServerLoop()
PostmasterMain(int argc, char ** argv)
main(int argc, char ** argv)

如上handle_sig_alarm的参数为 14,这就是上面信号SIGALRM

接下来的报错,如下:

因为这里报错级别是fatal error - abort process,进程退出,如下:

调试过程信号处理

因为上面的信号是SIGALRM,若是超时发送的是信号SIGINT 就例如StatementTimeoutHandler、LockTimeoutHandler等:

// src/backend/utils/init/postinit.c

/*
* STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt.
*/
static void
StatementTimeoutHandler(void)
{
int sig = SIGINT; /*
* During authentication the timeout is used to deal with
* authentication_timeout - we want to quit in response to such timeouts.
*/
if (ClientAuthInProgress)
sig = SIGTERM; #ifdef HAVE_SETSID
/* try to signal whole process group */
kill(-MyProcPid, sig);
#endif
kill(MyProcPid, sig);
} /*
* LOCK_TIMEOUT handler: trigger a query-cancel interrupt.
*/
static void
LockTimeoutHandler(void)
{
#ifdef HAVE_SETSID
/* try to signal whole process group */
kill(-MyProcPid, SIGINT);
#endif
kill(MyProcPid, SIGINT);
}

调试的时候就会被这些信号所打断,如下:

这些信号可以如下处理,就不再影响gdb调试,如下:

若是使用vscode调试,则可以如下设置:

注:关于调试过程中信号的处理和妙用 可以看一下建平的文档,如下:


遗留问题汇总分析

有了上面的铺垫,我们先看一下第一个问题:为什么该GUC参数的实现不能像statement_timeout那样,如下:

postgres=# set statement_timeout = '30s';
SET
postgres=# select pg_sleep(40);
2024-09-28 22:11:51.127 PDT [67675] ERROR: canceling statement due to statement timeout
2024-09-28 22:11:51.127 PDT [67675] STATEMENT: select pg_sleep(40);
ERROR: canceling statement due to statement timeout
postgres=#
postgres=# reset statement_timeout;
RESET
postgres=# show statement_timeout;
statement_timeout
-------------------
0
(1 row) postgres=# select pg_sleep(40);
^C2024-09-28 22:12:11.129 PDT [67675] ERROR: canceling statement due to user request
2024-09-28 22:12:11.129 PDT [67675] STATEMENT: select pg_sleep(40);
Cancel request sent
ERROR: canceling statement due to user request
postgres=#

statement_timeout超时,发送SIGINT 就像下面手动执行Ctrl + C。而transaction_timeout的如下:

postgres=# \d
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | t1 | table | postgres
(1 row) postgres=# table t1;
id
----
(0 rows) postgres=# set transaction_timeout = '30s';
SET
postgres=# begin ;
BEGIN
postgres=*# ^C
postgres=*# ^C
postgres=*# insert into t1 values (1);
INSERT 0 1
postgres=*# commit ;
COMMIT
postgres=# table t1 ;
id
----
1
(1 row) postgres=#

就像邮件列表里面的分析:secure_read() 里面处理不了 SIGINT 信号,通过发送 SIGINT 信号的方式没办法结束事务。之后原作者就将实现进行了更改,有兴趣的小伙伴可以自行查看patch v4以及之后的!


第二个问题:在与psql交互中 旧的会话因为事务超时而断开,然后怎么就又建立一个新的?如下:

如上psql进程还在,如下是restore逻辑:

// src/bin/psql/common.c

/* CheckConnection
*
* Verify that we still have a good connection to the backend, and if not,
* see if it can be restored.
*
* Returns true if either the connection was still there, or it could be
* restored successfully; false otherwise. If, however, there was no
* connection and the session is non-interactive, this will exit the program
* with a code of EXIT_BADCONN.
*/
static bool
CheckConnection(void);

transaction_timeout:达到事务超时时终止会话的更多相关文章

  1. rebuild online 创建时,会话被Kill修复索引测试

    rebuild online 创建时,会话被Kill修复索引 1.0实验目的:日常运维经常create index online,但是期间被kill会导致索引再次创建失败,测试解决该问题 2.0测试流 ...

  2. ORACLE查看会话的大小及终止会话

    一.出现PGA不足时,我们可以查看用户会话大小,结束相应会话 方法一 Select Server, Osuser, Name, Value / 1024 / 1024 Mb, s.Sql_Id, Sp ...

  3. C# 给某个方法设定执行超时时间 C#如何控制方法的执行时间,超时则强制退出方法执行 C#函数运行超时则终止执行(任意参数类型及参数个数通用版)

    我自己写的 /// <summary> /// 函数运行超时则终止执行(超时则返回true,否则返回false) /// </summary> /// <typepara ...

  4. 【转】Spring事务超时时间可能存在的错误认识

    1.先看代码 1.1.spring-config.xml <bean id="dataSource" class="org.springframework.jdbc ...

  5. System.Transactions 事务超时属性

    最近遇到一个处理较多数据的大事务,当进行至10分钟左右时,爆出事务超时异常,如果Machine.config中不设置最大超时时间,则默认超时时间为10分钟 MachineSettingsSection ...

  6. System.Transactions事务超时设置

    System.Transactions 有2个超时属性(timeout 与 maxTimeout),可以通过配置文件来进行设置. 1. timeout System.Transactions 默认的t ...

  7. WCF把书读薄(4)——事务编程与可靠会话

    WCF把书读薄(3)——数据契约.消息契约与错误契约 真不愧是老A的书,例子多,而且也讲了不少原理方面的内容,不过越读越觉得压力山大……这次来稍微整理整理事务和可靠会话的内容. 十八.事务编程 WCF ...

  8. Spring事务超时时间可能存在的错误认识

    摘自:http://jinnianshilongnian.iteye.com/blog/1986023, 感谢作者. 1.先看代码 1.1.spring-config.xml <bean id= ...

  9. jquery自定义滚动条 鼠标移入或滚轮时显示 鼠标离开或悬停超时时隐藏

    一.需求: 我需要做一个多媒体播放页面,左侧为播放列表,右侧为播放器.为了避免系统滚动条把列表和播放器隔断开,左侧列表的滚动条需要自定义,并且滚动停止和鼠标离开时要隐藏掉. 二.他山之石: 案例来自h ...

  10. phpMyAdmin:无法在发生错误时创建会话,请检查 PHP 或网站服务器日志,并正确配置 PHP 安装。

    一:错误提示 英文:Cannot start session without errors, please check errors given in your PHP and/or webserve ...

随机推荐

  1. RestSharp编写api接口测试,并实现异步调用(不卡顿)

    首先,确保你已经安装了RestSharp NuGet包.如果没有安装,可以通过以下命令安装: bash Install-Package RestSharp 然后,在你的C#代码中,你可以按照以下步骤操 ...

  2. 推荐一款.NET开源、功能强大的二维码生成类库

    前言 在日常开发需求中,生成二维码以分享文本内容或跳转至指定网站链接等场景是比较常见的.今天大姚给大家分享一款.NET开源(MIT License).免费.简单易用.功能强大的二维码生成类库:QrCo ...

  3. Hutool常用工具类

    1.背景 实际开发中经常用到很多的工具类,这里hutool提供了一系列的工具类,下面重点介绍常用的工具类. 2.使用步骤 官方文档:https://hutool.cn/docs/#/ 添加依赖 < ...

  4. Apache DolphinScheduler 3.1.8 版本发布,修复 SeaTunnel 相关 Bug

    近日,Apache DolphinScheduler 发布了 3.1.8 版本.此版本主要基于 3.1.7 版本进行了 bug 修复,共计修复 16 个 bug, 1 个 doc, 2 个 chore ...

  5. AC自动机 基础篇

    AC 自动机1 前置知识:\(KMP\),字典树. \(AC\) 自动机,不是用来自动 \(AC\) 题目的,而是用来处理字符串问题的(虽然确实可以帮助你 \(AC\)). 这里总结了 \(AC\) ...

  6. Grafana的仪表盘URL参数设置

    转载请注明出处: 在调试grafana时,需要对grafana进行share的url图表进行一些自定义设置,总结下常用的参数 参考网站:https://grafana.com/docs/grafana ...

  7. 使用一次sql请求,返回分页数据和总条数

    日常搬砖,总少不了需要获取分页数据和总行数. 一直以来的实践是编码两次sql请求,分别拉分页数据和totolCount. 最近我在思考: 常规实践为什么不是 在一次sql请求中中执行多次sql查询或多 ...

  8. k8s批量操作

    批量删除Evicted状态pod kubectl -n gitee get pods | grep Evicted |awk '{print$1}'|xargs kubectl -n gitee de ...

  9. C++11新特性(二):语言特性

    C++11新特性 nullptr空指针 nullptr空指针的使用可以规避掉以往设置为NULL的风险.NULL在编译器中常常被设置为0或者其它数字,此时判断指针是否为NULL,即判断指针类型是否能够等 ...

  10. Python3.11二进制AI项目程序打包为苹果Mac App(DMG)-应用程序pyinstaller制作流程(AppleSilicon)

    众所周知,苹果MacOs系统虽然贵为Unix内核系统,但由于系统不支持N卡,所以如果想在本地跑AI项目,还需要对相关的AI模块进行定制化操作,本次我们演示一下如何将基于Python3.11的AI项目程 ...