首页 > 其他分享 >transaction_timeout:达到事务超时时终止会话

transaction_timeout:达到事务超时时终止会话

时间:2024-10-02 14:45:50浏览次数:6  
标签:transaction statement void timeout TIMEOUT 超时 postgres

功能实现背景说明

我们已经有两个参数来控制长事务: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,statement,void,timeout,TIMEOUT,超时,postgres
From: https://www.cnblogs.com/rng-songbaobao/p/18444721

相关文章

  • 34_初识搜索引擎_search结果深入解析(search timeout机制揭秘)
    课程大纲1、我们如果发出一个搜索请求的话,会拿到一堆搜索结果,本节课,我们来讲解一下,这个搜索结果里的各种数据,都代表了什么含义2、我们来讲解一下,搜索的timeout机制,底层的原理,画图讲解GET/_search{"took":6,"timed_out":false,"_shards":{"total":6,"successful":6,......
  • http请求超时 ,你用golang是如何解决的
    http请求超时,你用golang是如何解决的?原创 磊丰 Go语言圈  2024年09月30日08:30 广东 听全文Go语言圈Go语言开发者的学习好助手,分享Go语言知识,技术技巧,学习与交流Go语言开发经验,互动才有助于技术的提升,每天5分钟,助你GO语言技术快乐成长195篇原创内容......
  • wait_event_interruptible_timeout() 函数
     原文链接:https://blog.csdn.net/wuyongpeng0912/article/details/45723657 网上有关于此函数的分析,但大都是同一篇文章转载来转载去,没有进一步的分析。做个小结:了解函数功能,除了直接看代码逻辑,最有效的当是注释内容了。如下:函数原型:wait_event_interruptible_timeout......
  • C# Task 实现任务超时取消、超时取消然后重试 超过重试最大次数就结束。
    C#Task实现任务超时取消、超时取消然后重试超过重试最大次数就结束。 任务超时取消示例publicstaticasyncTaskTimeoutCancelTask(){CancellationTokenSourcects=newCancellationTokenSource();//取消令牌Tasktask......
  • 如何投IEEE论文(Transactions on Cybernetics为例)
    文章目录1.下载对应的论文模板2.进入提交论文信息的界面3.填写论文中必要的信息3.1ArticleType3.2UploadManuscript3.3Title3.4Abstract3.5Authors3.6AuthorDetails3.7MathOrganizations3.8AdditionalInformation3.9FinalReview终审1.下载对应的论......
  • DDL 超时,应该如何解决 | OceanBase 用户问题集萃
    问题背景在OceanBase的社区问答里常看到有用户发帖提出DDL超时的问题, 如“执行DDL超时,为何调大超时时间不生效?”。但很多帖子的回答都没有完美解决。因此,这里把相关的解决思路在这里分享给大家。帖子里对这类问题的描述都很简单:就是执行了一条DDL,然后超时了,再然后把ob_......
  • 国外网站服务器访问超时怎么解决
    国外网站服务器访问超时可能是由于多种原因造成的,包括网络连接问题、服务器配置、DNS解析问题等。以下是一些可能的解决方案:检查网络连接:确认你的网络连接是否稳定。重启你的路由器或调制解调器。尝试在不同的设备上访问该网站,以确定问题是否出在特定设备上。刷新DNS缓存:在你的电脑......
  • Dbeaver安装驱动时连接超时的问题
    dbeaver现在真的是,版本升级上来了,但是各个地方都会遇到被墙的问题https://dbeaver.io/download/这个下载链接每次也是,安装包偶尔都有下载不下来的情况。 好不容易安装之后,创建数据库连接,安装驱动的时候,各种超时连接不上,基本都是死在maven访问不了,但是这时候你把地址 http......
  • 分布式环境中,接口超时重试带来的的幂等问题如何解决?
    目录标题幂等不能解决接口超时吗?幂等的重要性什么是幂等?为什么需要幂等?接口超时了,到底如何处理?如何设计幂等?幂等设计的基本流程实现幂等的8种方案1.select+insert+主键/唯一索引冲突(常用)2.直接insert+主键/唯一索引冲突3.状态机幂等(常用)4.抽取防重表5.token令牌(前......
  • Android studio 新建项目gradle依赖下载超时
    版本信息:android-studio-2024.1.2.12gradle-8.7&使用groovy配置项目配置:修改settings.gradle文件,将阿里云镜像仓库添加到google{}和mavenCentral()上方,不要随意改变仓库位置,仓库列出顺序决定 Gradle在这些仓库中搜索各个项目依赖项的顺序。pluginManagement{......