---
title: "Retry transactions that fail on a conflict · Agent Wranglers"
url: https://agentwranglers.se/playbook/data/transactions-and-concurrency/conflict-retries
id: DAT-81
level: Recommended
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1080
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Retry transactions that fail on a conflict

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Transactions and concurrency](https://agentwranglers.se/playbook/data/transactions-and-concurrency.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: DAT-81
- Level: Recommended
- Chapter: [Transactions and concurrency](https://agentwranglers.se/playbook/data/transactions-and-concurrency.md)
- Rule: Retry the whole transaction when the database cancels it for a serialisation failure or a deadlock. Cap the retries at 3, with a short random wait, and then return an error.
- Check: A test runs two serializable transactions that read and write the same row in crossed order. Both finish after at most 3 attempts, and the retry metric rises by at least 1. A test that always fails with `40001` gets an error after exactly 3 attempts.
- How: Under Repeatable Read and Serializable, PostgreSQL cancels a transaction that would break isolation with SQLSTATE `40001`. A deadlock gives `40P01` in PostgreSQL and error 1213 in MySQL. The PostgreSQL docs say the retry must rerun the complete transaction, including the code that decided what to write. Retrying only the failed statement can write a result based on stale reads. So wrap the transaction in one function and call it again. Retry nothing else by default. A unique violation may be a lasting error, not a passing one. Make sure the work inside has no side effects outside the database, or send them through [Send side effects through an outbox](https://agentwranglers.se/playbook/events/publishing-events/outbox.md). Count retries in a metric, since a rising rate means two code paths fight over the same rows.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
async function withSerializableRetry(work, maxAttempts = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await db.tx({ mode: new TransactionMode({ tiLevel: isolationLevel.serializable }) }, work);
    } catch (error) {
      const isRetryable = error.code === '40001' || error.code === '40P01';
      if (!isRetryable || attempt === maxAttempts) throw error;
      await new Promise(resolve => setTimeout(resolve, Math.random() * 50 * attempt));
    }
  }
}
```

### Python example

```python
connection.isolation_level = psycopg.IsolationLevel.SERIALIZABLE

def with_serializable_retry(work, max_attempts=3):
    for attempt in range(1, max_attempts + 1):
        try:
            with connection.transaction():
                return work(connection)
        except (psycopg.errors.SerializationFailure, psycopg.errors.DeadlockDetected):
            if attempt == max_attempts:
                raise
            time.sleep(random.random() * 0.05 * attempt)
```

## Sources

- [PostgreSQL serialization failure handling](https://www.postgresql.org/docs/current/mvcc-serialization-failure-handling.html)
- [MySQL deadlocks in InnoDB](https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks.html)

## Related rules

- DAT-15 [Know your isolation level](https://agentwranglers.se/playbook/data/transactions-and-concurrency/isolation-levels.md) in Data and databases
- EVT-6 [Send side effects through an outbox](https://agentwranglers.se/playbook/events/publishing-events/outbox.md) in Integration and events

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/data/transactions-and-concurrency/conflict-retries">Agent Wranglers data and databases playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Know your isolation level](https://agentwranglers.se/playbook/data/transactions-and-concurrency/isolation-levels.md)
- Next: [Make conditional writes](https://agentwranglers.se/playbook/data/transactions-and-concurrency/conditional-writes.md)
- [Transactions and concurrency](https://agentwranglers.se/playbook/data/transactions-and-concurrency.md)
