mysql 原则例外:尝试获取锁时发现死锁

xfb7svmp  于 2023-08-02  发布在  Mysql
关注(0)|答案(3)|浏览(131)

我有一个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

字符串
更新用户表时似乎无法获取锁。控制器代码如下:

/**
 * @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建议重试事务。我该怎么做?
可能是服务器的问题(访问太多),我必须提升服务器,或者代码错误,我必须在代码中显式处理死锁?

jyztefdp

jyztefdp1#

这是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
    }
}

字符串

jtoj6r0c

jtoj6r0c2#

Albert的解决方案是正确的,但您还必须使用ManagerRegistry的resetManager()在catch子句中重新创建一个新的EntityManager。如果您继续使用旧的EntityManager,您将获得异常,并且其行为将不可预测。也要注意对旧EntityManager的引用。
这个问题有望在学说3中得到纠正:See issue
在此之前,我的建议是妥善处理这个问题:Custom EntityManager

7d7tgy0s

7d7tgy0s3#

在@albert的基础上(thx)
1.将use添加到文件use Doctrine\DBAL\Exception\DeadlockException;的顶部
1.将此函数添加到一个类(例如服务)

public function retryflush($maxretry = 3)
    {
        $em = $this->doc->getManager();

        $retry=0;
        while ($retry >= 0) {
            try {
                $em->flush();
                $retry = -1;
            } catch (DeadlockException $e) {
                $retry++;
                if($retry>$maxretry) { throw $e; }
                sleep(513); //optional
            }
        }
        return $retry;
    }

字符串
1.重新定义

$em = $this->doc->getManager(); // use your own entitymanager.


1.就像

$classname->retryflush(5) // retry 5 times

相关问题