Исключение доктрины: обнаружен тупик при попытке получить блокировку

У меня есть приложение Symfony, которое предоставляет коллекцию веб-сервисов JSON, используемых мобильным приложением.
В последние несколько дней у нас много одновременных пользователей с помощью приложения (~ 5000 обращений в день), и ошибка «Доктрина» начала «случайно» появляться в моих журналах. Появляется примерно 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, но я не смог найти надежного решения. Доктрина предлагает повторить транзакцию, но не показывает реальный пример:

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
}

Это сообщения предлагает повторить транзакцию. Как мне это сделать?

Может ли это быть проблема с сервером (слишком много обращений), и я должен повысить сервер или код неправильный, и я должен явно обработать тупик в моем коде?

2

Решение

Это проблема 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
}
}
0

Другие решения

Решение Альберта является правильным, но вы также должен заново создайте новый EntityManager в предложении catch, используя resetManager () вашего ManagerRegistry. Вы получите исключения, если продолжите использовать старый EntityManager, и его поведение будет непредсказуемым. Остерегайтесь ссылок на старый EntityManager тоже.

Мы надеемся, что эта проблема будет исправлена ​​в Учении 3: Смотрите вопрос

До тех пор вот мое предложение хорошо справиться с этой проблемой: Пользовательский EntityManager

0

По вопросам рекламы [email protected]