更新相同行的多个进程导致mysql死锁?

时间:2014-10-30 12:03:51

标签: php mysql fork deadlock mariadb

我正在研究一个多次问自己的php程序,导致几个工作进程然后自动处理任务表。每个进程打开它自己的mysql连接(这是必须的,因为php的forking体系结构),然后应该处理它自己的任务(没有两个工作者应该在同一个任务上工作)。为此,工作人员在开始处理之前“接受”任务。这是通过以下方式实现的:

  1. 工作人员请求任务
  2. 启动一项交易,其中查询所有“待处理”任务
  3. 这些任务是迭代的。然后脚本尝试通过更新它的状态来“获取”每个任务。更新的条件为where status = 'pending',以确保在此期间未执行此操作。
  4. 一旦成功“获取”任务,迭代就会停止并提交事务。
  5. 我首先在mysql shell中测试了这个场景 - 在两个终端窗口中建立了两个与数据库的连接。两者都开始了交易。然后在第一个窗口中将任务更新为“已拍摄”。尝试在第二个窗口中更新相同的任务。然后第二个窗口等待,直到我在第一个窗口中提交事务并且只是礼貌地失败(0行受影响)。

    现在,在php(使用PDO)中,只要第二个进程尝试执行任务,我就会在db上遇到死锁:

      

    PHP致命错误:未捕获异常'PDOException',消息为'SQLSTATE [40001]:序列化失败:1213尝试获取锁定时发现死锁;尝试重启事务'in ...

    我不确定我是否应该只在事务中包装实际的更新任务。但是在我看来,我在程序中执行它的方式与我在shell上执行它的方式相同,因此应该可以工作。

    有人可以帮我解决我在这里缺少的事情吗?

2 个答案:

答案 0 :(得分:0)

你在InnoDB这样很好。

你的任务是否在桌子上?我认为他们是,但你没有这么明确地说。

尝试使用此规则来获取要工作的任务。

BEGIN;   /* start a transaction */
SELECT task_id, whatever
  FROM tasktable
 WHERE status = 'pending'
 LIMIT 1
FOR UPDATE;

然后,如果您返回一个带有任务ID的行,您就可以处理该任务了。如果没有返回任何行,则没有任何待处理的任务。

将任务标记为有效。

UPDATE tasktable 
   SET status = 'working' 
 WHERE task_id = <<the task ID you just got back>>;

然后,

COMMIT;

如果你遇到一个死锁异常(你可能仍会得到一个,但会更少)然后发出ROLLBACK;,然后再次尝试整个序列。

然后,完成任务。完成后将其标记为已完成。您不需要事务,因为您可以在一个查询中执行此操作。

UPDATE tasktable 
   SET status = 'complete' 
 WHERE task_id = <<the task ID you just got back>>;

如果您在客户端连接中关闭了自动提交,请不要​​忘记在该查询后提交。

答案 1 :(得分:0)

我找到了"solution" that I'm going with。如果有人有不同的解决方案,我很乐意听到它,如果我最终使用它,我会乐意接受你的答案。

所以基本上试图在循环中“接受”任务,当我遇到死锁时,我会回滚并再试一次。我的第一次测试表明,它解决了所有工人试图完成任务时的初始问题。