我有一个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建议重试该交易。我该怎么办?
可能是服务器问题(访问次数太多)而且我必须提升服务器或代码错误,我必须在代码中明确处理死锁?
答案 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