php 42 lines · 8 steps

Retrying database deadlocks in PHP

A transaction wrapper that automatically retries when the database reports a deadlock, backing off a little longer each time.

Explained by highlit
1public function transaction(callable $callback, int $maxAttempts = 3): mixed
2{
3 $attempt = 0;
4 
5 while (true) {
6 $attempt++;
7 $this->pdo->beginTransaction();
8 
9 try {
10 $result = $callback($this->pdo);
11 $this->pdo->commit();
12 
13 return $result;
14 } catch (PDOException $e) {
15 if ($this->pdo->inTransaction()) {
16 $this->pdo->rollBack();
17 }
18 
19 if (! $this->isDeadlock($e) || $attempt >= $maxAttempts) {
20 throw $e;
21 }
22 
23 $this->logger->warning('Deadlock detected, retrying transaction', [
24 'attempt' => $attempt,
25 'sqlstate' => $e->getCode(),
26 ]);
27 
28 usleep(random_int(50_000, 250_000) * $attempt);
29 }
30 }
31}
32 
33private function isDeadlock(PDOException $e): bool
34{
35 if ($e->getCode() !== '40001' && $e->getCode() !== '40P01') {
36 $driverCode = $e->errorInfo[1] ?? null;
37 
38 return in_array($driverCode, [1213, 1205], true);
39 }
40 
41 return true;
42}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Deadlocks are transient, so retrying the whole transaction is often the correct response rather than failing outright.
  2. 2Randomized, attempt-scaled backoff spreads out competing retries so they don't collide again immediately.
  3. 3Only retry errors you can specifically identify as safe — rethrow everything else so real failures still surface.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Retrying database deadlocks in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code