InnoDB detects a deadlock, kills one transaction and returns an error, and an application that treats that as a bug will treat a normal event as an outage.
foreach (range(1, 3) as $attempt) {
try {
DB::transaction(fn () => $this->applyPayment($order));
return;
} catch (QueryException $e) {
if ($e->getCode() !== '40001' || $attempt === 3) {
throw $e;
}
usleep(random_int(10_000, 50_000) * $attempt);
}
}
SQLSTATE 40001 is the serialisation failure and covers both deadlocks and lock wait timeouts, which want the same response. The retry must re-run the whole transaction rather than the failed statement, because the transaction was rolled back entirely — retrying one statement inside a dead transaction produces a different and more confusing error. Jitter on the backoff matters here for the same reason it matters anywhere: two transactions retrying in lockstep deadlock again.