如何使用Doctrine在死锁后重试事务?

8

我正在编写一个PHP函数,用于将大量数据存储/更新到表中,这可能会导致死锁。我尝试了解如何使用Doctrine重试失败的事务,但遗憾的是在网上找不到任何信息。最终,我编写了以下代码:

 $retry = 0;
 $done = false;
 while (!$done and $retry < 3) {
     try {

         $this->entityManager->flush();
         $done = true;

     } catch (\Exception $e) {
         sleep(1);

         $retry++;
     }
 }

 if ($retry == 3) {
     throw new Exception(
         "[Exception: MySQL Deadlock] Too many people accessing the server at the same time. Try again in few minutes"
     );
 }

我的问题:这种方法会不会导致数据库中存在重复数据?如果有,我该如何强制Doctrine回滚事务?

4个回答

17

死锁会返回错误代码1213,你需要在客户端处理该错误。

请注意,死锁和锁等待是不同的事情。在死锁中,没有“失败”的事务:它们都有错。无法保证哪一个事务将被回滚。

你必须使用rollback,否则你的样式代码会插入重复内容。例如,你应该:

$retry = 0;

$done = false;


$this->entityManager->getConnection()->beginTransaction(); // suspend auto-commit

while (!$done and $retry < 3) {

    try {

        $this->entityManager->flush();

        $this->entityManager->getConnection()->commit(); // commit if succesfull

        $done = true;

    } catch (\Exception $e) {

        $this->entityManager->getConnection()->rollback(); // transaction marked for rollback only

        $retry++;

    }

}

希望这能帮到你。


谢谢 :) 这至少给了我一个继续的想法。 - Rorchackh
22
实际上,这已经不再正确了。在出现异常EntityManager的情况下,它将进入关闭状态并抛出一个ORMException,指示实体管理器已关闭,并在第二次尝试时引发异常。Doctrine版本2.4.*。 - Mantas
这是如何在关闭EntityManager后重置它的方法:https://codedump.io/share/rjB45oiwtqwo/1/doctrine2-the-entitymanager-is-closed-how-to-reset-entity-manager-in-symfony2 - Ka.
2
Doctrine 2.6.*+有一个\Doctrine\DBAL\Exception\RetryableException异常,可以捕获Doctrine\DBAL\Exception\DeadlockExceptionDoctrine\DBAL\Exception\LockWaitTimeoutException文档在此我建议根据您的版本捕获这些异常,而不是通用异常。 - Valentin Silvestre

3

以下是我如何使用Sf2.7和doctrine 2.4.7处理重试失败交易的方法:

use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\ORM\EntityManager;

class Foo
{
    /**
     * @var Registry
     */
    protected $doctrine;

    public function __construct(Registry $registry)
    {
        $this->doctrine = $registry;
    }

    protected function doSomething($entity, $attempt)
    {
        $em = $this->getEntityManager();
        $conn = $em->getConnection();
        try{
            $conn->beginTransaction();
            $entity->setBar("baz");
            $em->flush();
            $conn->commit();
        } catch(\PDOException $e){
            $conn->rollBack();
            $attempt++;
            if($attempt <= 3){
                $this->doSomething($repayment, $attempt);
            }
        }
    }

    /**
     * @return EntityManager
     */
    protected function getEntityManager()
    {
        /** @var EntityManager $em */
        $em = $this->doctrine->getManager();
        if(!$em->isOpen()){
            $this->doctrine->resetManager();
            $em = $this->doctrine->getManager();
        }

        return $em;
    }
}

2

重试事务片段

  • 连接获取无需烦恼。
  • While循环已经得到适当处理。

代码

$connection = ...
$retry = 0;
$maxRetries = 3;

while ($retry < $maxRetries) {
    try {
        $connection->beginTransaction();

        // do stuff

        $connection->commit();

        break;
    } catch (Throwable $exception) {
        $connection->rollBack();

        $retry++;

        if ($retry === $maxRetries) {
            throw $exception;
        }
    }
}

关于Doctrine事务的更多信息可以在https://www.doctrine-project.org/projects/doctrine-dbal/en/2.7/reference/transactions.html找到。

如何获取连接:

只需通过构造函数注入Doctrine的EntityManager (Interface)或Connection即可。

连接

public function __construct(Connection $connection)
{
    $this->connection = $connection;
}

或者实体管理器

public function __construct(EntityManagerInterface $entityManager)
{
    $this->entityManager = $entityManager;
}

获取连接的代码为 $this->entityManager->getConnection()


-5

如果您没有使用 ORM,请使用它,它将自动管理死锁情况。


我没有使用ORM进行插入操作。你能解释一下死锁是如何自动处理的吗? - Rorchackh
1
如果两个表都有相互引用的外键,其中一个不能为 NOT NULL。如果所有映射正确,Doctrine 将始终持久化具有可为空约束的表,持久化具有义务约束的表 编辑第一个以设置正确的 FK 值。 - Pedro Cordeiro
3
Doctrine是一个ORM(对象关系映射),但它无法自动处理死锁情况。一旦出现数据库错误,它就会崩溃并且很难恢复。使用ORM的问题在于你实际上是将问题外包到别处,当这个地方出现问题时,修复起来可能会非常麻烦。这就是使用现成ORM的代价。 - StampyCode

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接