Skip to main content

Fixed: "RetryLimitExceededException: The maximum number of retries (3) was exceeded" in EF Core with MySQL

· 5 min read
Jagdish Kumawat
Founder @ Dewiride

A request took about 4 seconds, and then failed with RetryLimitExceededException: The maximum number of retries (3) was exceeded while executing database operations. That exception means EF Core did its job and ran out of attempts. The cause is one level down, and the client deserves a better answer than a 500.

What I Was Doing​

My .NET C# Web API uses EF Core 10.0.14 with a retry strategy that's allowed 3 retries. MySQL was stopped for a moment, and I sent a request:

Request
GET http://localhost:5190/demo/categories-count

What I Expected​

If the outage were short, the retries would cover it.

What I Got​

Response
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500,
"traceId": "00-63a50836c192130b8c37709fb2751bdb-20bc973b4ba33e10-00"
}
API log
A transient exception occurred during execution. The operation will be retried after 0ms.
A transient exception occurred during execution. The operation will be retried after 1038ms.
A transient exception occurred during execution. The operation will be retried after 3087ms.
Microsoft.EntityFrameworkCore.Storage.RetryLimitExceededException: The maximum number of retries (3) was exceeded while executing database operations with 'MySqlRetryStrategy'. See the inner exception for the most recent failure.

A request answering 500 after three retries, with the log lines ending in RetryLimitExceededException: The maximum number of retries (3) was exceeded.

What I Checked​

The inner exception. The message says to look there, and it holds the real error:

API log
MySql.Data.MySqlClient.MySqlException (0x80004005): Unable to connect to any of the specified MySQL hosts
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Unable to connect to any of the specified MySQL hosts

The timing. The delays were 0ms, about 1 second and about 3 seconds, so 3 retries took a bit over 4 seconds:

Output (status, and how long it took)
500 4.734215s

So an outage shorter than that is covered, and a longer one isn't. A docker compose restart was short enough here, and every request got through.

Why It Happens​

EF Core's execution strategy repeats a failed operation up to maxRetryCount times, with growing delays capped at maxRetryDelay. When the last attempt fails, it stops and throws this, with the last failure inside. It isn't an error of its own. It's a wrapper that says the outage lasted longer than the retries.

The Fix​

First, deal with the inner exception. Here, MySQL was down, so start it. If it's Too many connections or a deadlock, the fix is different: read what's inside.

Then, give the client a useful answer. The database can't do this right now, and it isn't the client's fault. That's a 503, with a time to come back:

src/Dewiride.Inventory.Api/Infrastructure/DatabaseUnavailableExceptionHandler.cs (excerpt)
private static bool IsTemporary(Exception exception) =>
exception is RetryLimitExceededException
|| exception.FindMySqlException() is { Number: 0 or 1205 or 3024 };

How to Return 503 with a Retry-After Header in ASP.NET Core has the whole handler.

If outages here are usually longer, raise MaxRetryCount in appsettings.json. Each retry keeps a request waiting, so don't raise it far.

After the Fix​

Response
HTTP/1.1 503 Service Unavailable
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.4",
"title": "The database can't do this right now.",
"status": 503,
"detail": "Try again in 5 seconds. Send the same Idempotency-Key, so a change can't be applied twice.",
"traceId": "00-0d0b17fbe59f130e78f5555cbee67b94-a6ae5b85e2201e17-00"
}

With MySQL running again, the same request worked straight away:

Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"count": 4
}

The same request answering 503 Service Unavailable with Retry-After: 5, then 200 OK once MySQL was running again.

Common Mistakes​

MistakeWhat happensDo this instead
Reading only the outer messageYou don't see the causeRead the inner exception
Raising MaxRetryCount to 50Requests hang for minutesKeep it small, and answer 503
Answering 500Clients assume a bug, and don't retryAnswer 503 with Retry-After
Adding a lock wait error to the listEach try waits the whole limitLeave it out

Troubleshooting​

SymptomLikely causeFix
The inner error is Unable to connect to any of the specified MySQL hostsMySQL is down, or unreachableThe fix
The inner error is a deadlockThe same rows keep collidingFix the lock order
This never appears, and there are no retriesThe errors aren't listedList them

Conclusion​

RetryLimitExceededException means the retries ran out, and the real error is inside it. Fix that error, and answer 503 with Retry-After, so a client knows it's worth trying again.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.