Prevent Laravel Rate Limiter Deadlocks with MySQL

Laravel can store rate-limit counters in its database cache, but a burst of requests can make many transactions contend for the same expired key. For a hot limiter, give the limiter a Redis store and make one atomic increment per request.

Last updated: November 3, 2026.

<?php
// config/cache.php
return [
    'default' => env('CACHE_STORE', 'database'),
    'limiter' => env('CACHE_LIMITER', 'redis'),
];

// One atomic operation returns the new count.
$count = RateLimiter::increment($key, amount: 1);
if ($count > $limit) {
    throw new TooManyRequestsHttpException($retryAfter);
}

Set CACHE_LIMITER=redis in the environment and clear cached configuration after deployment. The application’s normal cache can remain on another store.

Reduce contention before adding retries

The official Laravel rate-limiting documentation lets the limiter use a dedicated cache store and notes that Redis, Memcached, and database increments are atomic. Atomicity prevents lost counts, but it does not guarantee that a relational database will never choose a deadlock victim under heavy contention.

Avoid a separate tooManyAttempts() check followed by increment() on very busy endpoints. The returned count from one increment gives each request a consistent result with fewer cache operations. Choose keys with the intended scope—user, tenant, IP address, or action—and include a stable separator so unrelated limits cannot collide.

Diagnose and recover from error 1213

If MySQL remains the limiter store, capture SHOW ENGINE INNODB STATUS immediately after a failure and temporarily enable full deadlock logging when needed. MySQL’s InnoDB error-handling documentation says a deadlock rolls back the entire transaction and the application should retry the entire transaction.

Keep retries bounded, add randomized backoff, and repeat only idempotent work. Do not retry just the failed statement inside a transaction whose other changes may have been rolled back. Keep email, payments, and remote API calls outside the retry loop.

Monitor limiter latency, deadlock count, and Redis availability after the change. A fallback to database cache can be useful, but it should be deliberate and observable rather than silently shifting a traffic spike back to MySQL. Continue with transaction recovery after deadlocks, tenant-safe Redis keys, and SaaS rate-limit design.

Related Web Cheat Sheet guides

Sergey Kornilov

Sergey Kornilov