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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Deadlocks are transient, so retrying the whole transaction is often the correct response rather than failing outright.
- 2Randomized, attempt-scaled backoff spreads out competing retries so they don't collide again immediately.
- 3Only retry errors you can specifically identify as safe — rethrow everything else so real failures still surface.
Related explainers
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Streaming a filtered CSV export in Laravel
streaming
csv-export
query-builder
Intermediate
9 steps
rust
use std::cmp::Ordering; use std::str::FromStr; #[derive(Debug, Clone, PartialEq, Eq)]
Parsing and ordering semantic versions in Rust
parsing
trait-implementation
ordering
Intermediate
8 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
javascript
'use client'; import { useEffect } from 'react'; import * as Sentry from '@sentry/nextjs';
How a Next.js error boundary recovers
error-boundary
error-handling
observability
Intermediate
8 steps
ruby
class Registration < ApplicationRecord belongs_to :event validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
Validating registrations in Rails
validations
i18n
error-handling
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/retrying-database-deadlocks-in-php-explained-php-1d0e/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.