如何处理 Doctrine 中的僵局?

Tom*_*Tom 3 php mysql deadlock symfony doctrine-orm

我有一个基于 Symfony 的移动应用程序和服务器,它为移动应用程序提供 API。

我有一种情况,用户可以喜欢Post. 当用户喜欢Post我在 ManyToMany 表中添加这个特定用户喜欢这个特定的条目时Post(步骤 1)。然后在Post表中我增加 likesCounter(步骤 2)。然后在User表中我增加了用户的游戏化点数(因为他喜欢Post)(第 3 步)。

因此,存在许多用户同时喜欢特定Post并且发生死锁(在Post桌子上或User桌子上)的情况。如何处理?在Doctrine Docs 中,我可以看到这样的解决方案:

<?php

try {
    // process stuff
} catch (\Doctrine\DBAL\Exception\RetryableException $e) {
    // retry the processing
}
Run Code Online (Sandbox Code Playgroud)

但我应该怎么做catch?重试整个点赞过程(步骤 1 到 3),例如 3 次,如果失败,将 BadRequest 返回到移动应用程序?或者是其他东西?

我不知道这是否是一个很好的例子,因为也许我可以尝试重建进程,这样就不会发生死锁,但我想知道如果它们真的发生了我该怎么办?

小智 8

我不同意 Stefan,死锁是正常的,正如 MySQL 文档所说:

通常,您必须编写应用程序,以便它们随时准备在事务因死锁回滚时重新发出事务。

请参阅:MySQL 文档

但是,Stefan 建议的循环是正确的解决方案。除了它缺少一个重要的点:在 Doctrine 抛出异常之后,EntityManager 变得不可用,您必须在 catch 子句中使用来自 ManagerRegistry 实例的 resetManager() 创建一个新的

当我和你有同样的担忧时,我在网上搜索,但没有找到任何完全令人满意的答案。所以我弄脏了我的手,回来了一篇文章,你会发现我上面所说的一个实现示例:

使用 Doctrine 的线程安全业务逻辑


Ste*_*ciu 1

我要做的是将所有喜欢发布到队列中,并使用批处理消费者使用它们,以便您可以将更新分组到单个帖子上。

如果您坚持保持当前的实施,您可以按照您自己建议的方式进行:

<?php

for ($i = 0; $i < $retryCount; $i++) {
  try {
      // try updating
      break;
  } catch (\Doctrine\DBAL\Exception\RetryableException $e) {
      // you could also add a delay here
      continue;
  }
}

if ($i === $retryCount) {
  // throw BadRequest
}
Run Code Online (Sandbox Code Playgroud)

这是一个丑陋的解决方案,我不会建议它。不应该通过重试或使用延迟来“避免”死锁。还要查看命名锁并使用相同的重试系统,但不要等待死锁发生。