Doctrine异常:尝试锁定时发现死锁

时间:2017-12-19 10:48:31

标签: php mysql symfony doctrine-orm deadlock

我有一个Symfony应用程序,它公开了一个移动应用程序使用的JSON Web服务集合 在过去的几天里,我们有许多并发用户使用该应用程序(每天约5000次访问),并且随机启动了一个Doctrine错误"出现在我的日志中。它每天出现大约2-3次,这是错误:

Uncaught PHP Exception Doctrine\DBAL\Exception\DriverException: "An exception occurred while executing 'UPDATE fos_user_user SET current_crystals = ?, max_crystals = ?, updated_at = ? WHERE id = ?' with params [31, 34, "2017-12-19 09:31:18", 807]:

SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction" at /var/www/html/rollinz_cms/releases/98/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php line 115

在更新users表时似乎无法获取锁定。控制器代码如下:

/**
 * @Rest\Post("/api/badges/{id}/achieve", name="api_achieve_badge")
 */
public function achieveAction(Badge $badge = null)
{
    if (!$badge) {
        throw new NotFoundHttpException('Badge not found.');
    }

    $user      = $this->getUser();
    $em        = $this->getDoctrine()->getManager();
    $userBadge = $em->getRepository('AppBundle:UserBadge')->findBy(array(
        'user'  => $user,
        'badge' => $badge,
    ));

    if ($userBadge) {
        throw new BadRequestHttpException('Badge already achieved.');
    }

    $userBadge = new UserBadge();
    $userBadge
        ->setUser($user)
        ->setBadge($badge)
        ->setAchievedAt(new \DateTime())
    ;
    $em->persist($userBadge);

    // sets the rewards
    $user->addCrystals($badge->getCrystals());

    $em->flush();

    return new ApiResponse(ApiResponse::STATUS_SUCCESS, array(
        'current_crystals'    => $user->getCurrentCrystals(),
        'max_crystals'        => $user->getMaxCrystals(),
    ));
}

我查看了MySQL和Doctrine文档,但我找不到可靠的解决方案。 Doctrine建议重试交易,但它没有显示实际的例子:

https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-example.html

try {
    // process stuff
} catch (\Doctrine\DBAL\Exception\RetryableException $e) {
    // retry the processing
}

This posts建议重试该交易。我该怎么办?

可能是服务器问题(访问次数太多)而且我必须提升服务器或代码错误,我必须在代码中明确处理死锁?

2 个答案:

答案 0 :(得分:0)

这是一个MySQL问题。阻止相同资源的多个同时事务。

检查您是否有可能长时间阻止记录的cronjobs。

否则只是并发请求更新相同的数据,您可能更好地了解此数据的更新位置。

在php中重试的肮脏尝试:

$retry=0;
while (true) {
    try {
      // some more code
      $em->flush();
      return new ApiResponse(ApiResponse::STATUS_SUCCESS, array(
         'current_crystals'    => $user->getCurrentCrystals(),
         'max_crystals'        => $user->getMaxCrystals(),
      ));
    } catch (DriverException $e) {
       $retry++;
       if($retry>3) { throw $e; }
       sleep(1); //optional
    }
}

答案 1 :(得分:0)

阿尔伯特的解决方案是正确的选择,但您也必须必须使用ManagerRegistry的resetManager()在catch子句中重新创建一个新的EntityManager。如果您继续使用旧的EntityManager,将会获得异常,并且其行为将不可预测。还要提防对旧EntityManager的引用。

该问题有望在学说3中得到纠正:See issue

在此之前,我建议您很好地解决该问题:Custom EntityManager