MySQL 回滚事务与丢失/断开连接
Posted
技术标签:
【中文标题】MySQL 回滚事务与丢失/断开连接【英文标题】:MySQL rollback on transaction with lost/disconnected connection 【发布时间】:2019-09-21 23:17:48 【问题描述】:我需要让 mysql 服务器在其客户端断开连接后立即回滚事务,因为每个客户端同时工作。可以像这样重现问题(使用 innodb 表类型) 在客户端 A 上:
START TRANSACTION;
SELECT MAX(ID) FROM tblone FOR UPDATE;
#... then disconnect your connection to the server
在客户端 B:
START TRANSACTION;
SELECT MAX(ID) FROM tblone FOR UPDATE;
#... lock wait time out will occur here
我设置了 MySQL 的服务器选项,如 innodb_rollback_on_timeout
,并在两个客户端上都使用了 mysql 的客户端 mysql --skip-reconnect
。我在网络上使用一台服务器和两个客户端进行了尝试。我在SELECT ... FOR UPDATE;
线之后物理地断开了网络(拔下电缆)。我需要让其他客户端能够立即在事务上使用tblone
(锁定它,更新它),为此我认为服务器应该在客户端 A 断开连接后回滚客户端 A 的事务。
【问题讨论】:
有趣的问题。我以为这是自动的!所以我们需要像innodb_rollback_on_disconnect
.. 这样的东西,那太好了,我想说,这应该是默认的!这将是对 mysql 的合理更改请求。
@Tomas 在 2012 年首次提出这个问题后,我遇到了同样的问题!我编写了一个简单的 python 脚本来模拟这个问题,发现如果第一个客户端失去连接,第二个客户端会立即获得锁。但是我不明白MySQL是怎么做到的,请参考github.com/qiulang/mysql
【参考方案1】:
当您在物理上断开客户端连接时,您不会发送正常的断开连接(这会导致回滚),并且 MySQL 协议不是很健谈,因此服务器永远不会知道客户端不存在。与客户端和服务器在内部进行更多对话的其他数据库系统相比,我认为这是协议中的一个缺陷。
无论如何。您可以更改两个变量。他们基本上做同样的事情,但针对不同的客户。
第一个是wait_timeout,它被java或php等应用程序客户端使用。
另一个是interactive_timeout,它被mysql客户端使用(在你的测试中)
在这两种情况下,服务器都会在几秒钟后终止连接,这样做时会回滚所有事务并释放所有锁。
【讨论】:
感谢您的回复,我已经尝试使用这两个选项,并将它们设置为 60 秒(用于实验),但出现了另一个问题。在 60 秒不活动(空闲)后,连接自动关闭,下一个查询产生错误(服务器已消失),然后自动重新连接。我是否需要编写一些代码以每 59 秒查询一次以确保连接有效?还是有其他方法?超过 60 秒的长查询是否会在处理过程中断开连接? 因为我只在事务中需要这种行为,我可以在事务之前执行SET SESSION wait_timeout = 60
之类的操作并在提交/回滚之后恢复它吗?
您应该能够在存储过程中更改它,或者在开始事务之前将其作为单独的语句进行更改。你对空闲连接被关闭是正确的。这就是它的工作原理。但是,就我所见,长时间运行的查询不算作“空闲”,因此应该是安全的(易于使用select 1, sleep(61) from dual
进行测试)
哇,谢谢,所以我只是在START TRANSACTION;
之前设置了SET @old_wait_timeout := @@session.wait_timeout; SET @@session.wait_timeout := 60;
,然后在COMMIT
或ROLLBACK
之后使用SET @@session.wait_timeout := @old_wait_timeout;
恢复它。我希望这有效
@qsoft 我的理解:wait_timeout 是非活动超时。表示每次激活后都会清除并重启,例如:表扫描、行插入、收到网络包等。所以在事务下,我们可以将此选项设置为较小的值(例如:5秒)。这将更适合错误检测和故障恢复。【参考方案2】:
这是讨论的一些评论。请注意,这与某些评论不一致。我将使用 INSERT 而不是 SELECT..FOR UPDATE,因为效果更明显。
让我们看一些不同的案例:
(1) 无 SQL + 超时
START TRANSACTION;
do some SQL statement(s)
do no SQL for more than the timeout (before COMMITing)
由于下面详述的情况,请避免这种情况。解决方案:不要依赖 InnoDB 来帮助您处理长事务。
(2) 长时间运行的查询
START TRANSACTION;
do some SQL statement(s)
run an SQL query that takes more than the timeout
COMMIT;
一切都好。只要服务器 (mysqld) 继续执行查询,超时就不适用。也就是说,超时'时钟'在每个 SQL 语句的末尾重新开始
(3)(自动重新连接)
START TRANSACTION;
INSERT ... VALUES (123);
time passes; no SQL performed for longer than the timeout
disconnect occurs
INSERT ... VALUES (456);
auto-reconnect (because you have it ENabled);
the INSERT proceeds
COMMIT;
123 将被回滚; 456 将被插入。 (同样 SELECT..FOR UPDATE 会丢失锁。)不好。解决方案是关闭“自动重新连接”。相反,检查错误并将断开连接错误视为事务的致命错误。 (然后重新开始事务。)
INSERT 456 将在由autocommit
控制的新事务中运行。
(4)(无自动重新连接)
START TRANSACTION;
INSERT ... VALUES (123);
time passes; no SQL for longer than the timeout
disconnect occurs
INSERT ... VALUES (456);
NO auto-reconnect (because you have it DISabled)
COMMIT;
123 将被回滚。 456 的 INSERT 会出现类似“连接丢失”的错误。重新开始交易。
【讨论】:
感谢您的回答。我可能需要指出,您在此处描述的内容更有可能发生在 mysql 客户端而不是正在运行的程序中。您的答案更适用于 DBA 问题而不是编程问题。顺便说一句,wait_timeout 的默认值为 8 小时。我觉得对于正在运行的线程/进程来说太长了 应该是innodb_lock_wait_timeout
,默认为50秒。
我会写一些代码来验证它。应该很容易验证。
您好,我写了一个简单的 python 脚本来测试我们的讨论。但令我惊讶的是,当第一个进程返回而没有明确关闭数据库连接(模拟崩溃)时,第二个进程总是立即通知。你能看看吗? github.com/qiulang/mysql
@Qiulang - 我在crash_test.py
中看不到任何与START TRANSACTION
等效的内容以上是关于MySQL 回滚事务与丢失/断开连接的主要内容,如果未能解决你的问题,请参考以下文章
当 *** 连接/断开连接时,ConnectivityManager.NetworkCallback 调用丢失