为MySQL / InnoDB中的事务设置时间限制


11

这是从这个相关的问题产生的,在这里我想知道如何在一个琐碎的情况下(两个都只在一行上进行操作)强制两个事务按顺序进行。我得到了一个答案– SELECT ... FOR UPDATE用作两个事务的第一行–但这导致了一个问题:如果从不提交或回滚第一个事务,则第二个事务将被无限期地阻止。innodb_lock_wait_timeout变量设置了秒数,在此秒数之后,尝试进行第二次事务的客户端将被告知“对不起,再试一次” ...但是据我所知,他们将再次尝试直到下一个服务器重新启动。所以:

  1. ROLLBACK如果交易永存,肯定有一种方法可以强制执行?我必须诉诸使用守护程序来杀死此类事务,如果是的话,这样的守护程序将是什么样?
  2. 如果某个连接被交易中wait_timeoutinteractive_timeout交易中止,交易是否回滚?有没有办法从控制台进行测试?

澄清度innodb_lock_wait_timeout设置事务放弃放弃之前等待释放锁的秒数;我想要的是一种强制释放锁的方法

更新1:这是一个简单的示例,演示了为什么innodb_lock_wait_timeout不足以确保第二个事务不会被第一个事务阻止:

START TRANSACTION;
SELECT SLEEP(55);
COMMIT;

使用默认设置innodb_lock_wait_timeout = 50,此事务在55秒后完成而没有错误。并且,如果您UPDATE在该SLEEP行之前添加一个,然后从另一个尝试到SELECT ... FOR UPDATE同一行的客户端发起第二笔交易,那是第二笔交易超时,而不是睡着的那笔交易。

我正在寻找的是一种方法来结束此笔交易的沉睡。

更新2:为了回应hobodave对上面示例的现实性的担忧,这是另一种情况:DBA连接到实时服务器并运行

START TRANSACTION
SELECT ... FOR UPDATE

第二行锁定了应用程序经常写入的行。然后,DBA被打断并走开,忘记了结束交易。该应用程序停止运行直到该行被解锁。我想尽量减少由于此错误而导致应用程序停滞的时间。


您刚刚更新了问题,使其与最初的问题完全冲突。您以粗体指出“如果从不提交或回滚第一个事务,那么将无限期地阻止第二个事务。” 您用最新的更新来证明这一点:“这是第二个超时的事务,而不是入睡的事务。” -认真吗?
hobodave 2011年

我想我的用语本来应该更清楚。“无限期阻止”是指第二个事务将超时,并显示一条错误消息“尝试重新启动事务”。但这样做将无济于事,因为它将再次超时。因此,第二笔交易被阻止了-它永远不会完成,因为它将一直超时。
Trevor Burnham

@Trevor:您的用语非常清楚。您只需将问题更新为完全不同的问题即可,这是非常糟糕的形式。
hobodave 2011年

问题始终是相同的:当从不提交或回滚第一个事务时,无限期地阻塞第二个事务是一个问题。如果要ROLLBACK花费超过n几秒钟的时间来完成,我想强制执行第一个事务。有什么办法吗?
Trevor Burnham'3

1
@TrevorBurnham我也想知道为什么MYSQL没有配置来防止这种情况。由于客户端不负责任,因此服务器挂起是不可接受的。我不难理解您的问题,它是如此重要。
Dinoop paloli 2014年

Answers:



3

由于在ServerFault上已询问您的问题,因此可以合理地假设您正在寻找针对MySQL问题的MySQL解决方案,尤其是在系统管理员和/或DBA具有专业知识的知识范围内。部分解决您的问题:

如果第一笔交易从未提交或回滚,则第二笔交易将被无限期阻止

不,不会。我认为你没有理解innodb_lock_wait_timeout。它完全满足您的需求。

它将返回错误,如手册中所述:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • 根据定义,这不是不确定的。如果您的应用程序重新连接并反复阻塞,则您的应用程序将“无限期阻塞”,而不是事务。第二笔交易肯定会阻塞innodb_lock_wait_timeout几秒钟。

默认情况下,事务不会回滚。应用程序代码负责决定如何处理此错误(是重试还是回滚)。

如果要自动回滚,则手册中也对此进行了说明:

当前事务不会回滚。(要使整个事务回滚,请使用--innodb_rollback_on_timeout选项启动服务器。


RE:您的大量更新和评论

首先,您在评论中表示要“表示”,表示您希望有一种方法可以使第一个无限期阻塞的事务超时。从您最初的问题中看不出来,并且与“如果从不提交或回退第一个事务,那么第二个事务将被无限期地阻止”冲突。

尽管如此,我也可以回答这个问题。MySQL协议没有“查询超时”。这意味着您不能使第一个被阻止的事务超时。您必须等到完成为止,否则终止会话。终止会话后,服务器将自动回滚事务。

唯一的其他选择是使用或编写一个利用非阻塞I / O的mysql库,该库将允许您的应用程序在N秒后杀死进行查询的线程/分支。这种库的实现和使用超出了ServerFault的范围。这是适用于StackOverflow的问题。

其次,您在评论中陈述了以下内容:

实际上,我对问题的担忧是,在事务处理过程中客户端应用程序挂起(例如,陷入无限循环),而不是在MySQL结束时事务处理需要很长时间。

这在您最初的问题中一点都不明显,现在仍然没有。只有在评论中分享了这个相当重要的花絮之后,才能看出这一点。

如果这实际上是您要解决的问题,那么恐怕您在错误的论坛上提出了疑问。您已经描述了一个应用程序级编程问题,该问题需要一种编程解决方案,而MySQL无法提供该解决方案,并且不在本社区的范围之内。您的最新答案回答了“如何防止Ruby程序无限循环?”这一问题。该问题对于该社区而言是题外话,应在StackOverflow上提出。


很抱歉,当交易正在等待锁(如名称和文件是真实的innodb_lock_wait_timeout暗示),也不是那么在我提出的情况:在客户端挂起或崩溃之前,它会换做一个COMMITROLLBACK
特雷弗·伯纳姆

@特雷弗:你错了。这对于您的测试来说是微不足道的。我只是在最后测试了它。请收回您的否决票。
hobodave 2011年

@hobo,只是因为您的权利并不意味着他必须喜欢它。
克里斯S

克里斯,你能解释一下他是对的吗?如果没有COMMIT或没有ROLLBACK消息发送来解决第一笔交易,那么第二笔交易将如何发生?Hobodave,如果您提供测试代码,可能会有所帮助。
特雷弗·伯纳姆

2
@Trevor:你没有一点道理。您要序列化交易,对不对?是的,您在另一个问题中是这样说的,并在此问题中重复此步骤。如果第一笔交易尚未完成,那么您究竟希望第二笔交易完成吗?您已明确告诉它等待该行上的锁被释放。因此,必须等待。您对此不了解?
hobodave 2011年

1

在类似情况下,我的团队决定使用pt-kill(https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html)。它可以报告或杀死运行时间过长的查询。然后可以每X分钟在cron中运行一次。

问题是执行时间异常长(例如,不确定)的查询锁定了一个备份过程,该备份过程尝试使用读锁定刷新表,从而又将所有其他查询锁定在“等待表刷新”上,然后再不超时因为他们不等待锁,而锁不会导致应用程序中没有错误,最终不会导致向管理员发出警报,也不会导致应用程序停止。

解决方法显然是修复了最初的查询,但是为了避免将来意外发生这种情况,如果可以自动终止(或报告)持续时间超过特定时间的交易/查询,那将是很好的选择,问题的作者似乎在问。这可以通过pt-kill来实现。


0

一个简单的解决方案是拥有一个存储过程来终止查询,这些查询所花费的时间比您所需的timeout时间还要多,innodb_rollback_on_timeout并带有使用选项。


-2

谷歌搜索一段时间后,似乎没有直接设置每个交易时间限制的方法。这是我能够找到的最佳解决方案:

命令

SHOW PROCESSLIST;

为您提供了一个表,其中包含当前正在运行的所有命令以及自它们启动以来的时间(以秒为单位)。当客户端停止发出命令时,它们被描述为发出Sleep命令,该Time列是自上一次命令完成以来的秒数。因此,如果您确信数据库中的任何查询都不会花费超过5秒的时间,那么您可以手动输入KILL所有Time值超过5秒的数据。例如,如果ID为3的进程的Time列中的值为12 ,则可以执行

KILL 3;

KILL语法的文档建议由具有该ID的线程执行的事务将回滚(尽管我尚未对此进行测试,因此请谨慎)。

但是如何自动执行此操作并每5秒杀死所有超时脚本?在同一页面上的第一条注释为我们提供了提示,但是代码是用PHP编写的;我们似乎不能做一个SELECT关于SHOW PROCESSLIST从MySQL客户端中。尽管如此,假设我们有一个每秒运行的PHP守护程序$MAX_TIME,它可能看起来像这样:

$result = mysql_query("SHOW FULL PROCESSLIST");
while ($row=mysql_fetch_array($result)) {
  $process_id=$row["Id"];
    if ($row["Time"] > $MAX_TIME) {
      $sql="KILL $process_id";
      mysql_query($sql);
  }
}

请注意,这将迫使所有空闲的客户端连接重新连接,而不仅仅是行为不正常的客户端。

如果有人有更好的答案,我很想听听。

编辑:使用KILL这种方式至少有一个严重的风险。假设您对KILL空闲10秒钟的每个连接使用上面的脚本。连接闲置10秒钟后,但在KILL发出连接之前,被终止连接的用户将发出START TRANSACTION命令。然后他们被KILL教育。他们发送UPDATE,然后三思而后行ROLLBACK。但是,由于KILL回滚了原始事务,因此更新立即完成,无法回滚!有关测试案例,请参阅此文章


2
此评论供此答案的将来读者使用。即使已接受答案,也请不要这样做。
hobodave 2011年

是的,而不是低估并留下一个卑鄙的评论,如何解释为什么这将是一个坏主意呢?发布原始PHP脚本的人说,它可以防止服务器挂起。如果有更好的方法可以实现相同的目标,请告诉我。
Trevor Burnham

6
评论不是is亵。这是对所有未来读者的警告,请不要尝试使用您的“解决方案”。自动终止长期运行的事务是一个单方面的想法。它应该永远也做不到。那就是解释。终止会话应该是一个例外,而不是常规。人为问题的根本原因是用户错误。您不会为锁定行然后“走开”的DBA创建技术解决方案。你开除他们。
hobodave 2011年

-2

正如hobodave在评论中建议的那样,在某些客户端中(尽管显然不是mysql命令行实用程序)可以为事务设置时间限制。这是在Ruby中使用ActiveRecord的演示:

require 'rubygems'
require 'timeout'
require 'active_record'

Timeout::timeout(5) {
  Foo.transaction do
    Foo.create(:name => 'Bar')
    sleep 10
  end
}

在此示例中,事务在5秒钟后超时并自动回滚。(根据hobodave的评论进行更新:如果数据库需要5秒钟以上的时间进行响应,则该事务将尽快回滚,而不是尽快回滚。)如果要确保所有事务在n几秒钟后超时,您可以围绕ActiveRecord构建包装器。我猜想这也适用于Java,.NET,Python等中最受欢迎的库,但我尚未对其进行测试。(如果有,请对此答案发表评论。)

KILL与从命令行完成的事务不同,ActiveRecord中的事务还具有在发出a时很安全的优点。参见/dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc

除非通过类似我在其他答案中发布的脚本的脚本,否则似乎不可能在服务器端强制执行最大交易时间。


您忽略了ActiveRecord使用同步(阻塞)I / O。该脚本所做的只是中断Ruby sleep命令。如果您的Foo.create命令阻塞了5分钟,则超时将不会杀死它。这与您的问题中提供的示例不兼容。SELECT ... FOR UPDATE与其他任何查询一样,A 很容易长时间阻塞。
hobodave 2011年

应该注意的是,几乎所有的mysql客户端库都使用阻塞I / O,因此在我的回答中建议您需要使用或编写自己的使用非阻塞I / O的库。这是超时没有用的简单演示:gist.github.com/856100
hobodave 2011年

实际上,我对问题的担忧是,在事务处理过程中客户端应用程序挂起(例如,陷入无限循环),而不是在MySQL结束时事务处理需要很长时间。但是,谢谢,这是一个好点。我已经更新了问题以备注意。)
Trevor Burnham

我无法复制您声明的结果。我更新了要利用的要点ActiveRecord::Base#find_by_sqlgist.github.com/856100再次,这是因为AR使用了阻塞I / O。
hobodave 2011年

@hobodave是的,该编辑是错误的,已得到纠正。需要明确的是:该timeout方法将回滚事务,但是要等到MySQL数据库响应查询为止。因此,该timeout方法适用于防止客户端上的长事务或由几个相对较短的查询组成的长事务,但不适用于以单个查询为元凶的长事务。
Trevor Burnham
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.