Troubleshooting
PostgreSQL & databases

Fix "Current Transaction Is Aborted, Commands Ignored" in Django

Fix the PostgreSQL InternalError current transaction is aborted, commands ignored until end of transaction block in Django: find the first failing query, use atomic savepoints around expected errors, and clean up after exceptions in tests and workers.

Problem statement

The traceback in Sentry points at an innocent query:

django.db.utils.InternalError: current transaction is aborted,
commands ignored until end of transaction block

The query in the traceback is not the problem. In PostgreSQL, once any statement in a transaction fails, every later statement in the same transaction is rejected with this message until the transaction is rolled back. Somewhere earlier, a query failed, your code caught the exception and kept going, and the next query hit the aborted transaction.

How the transaction abortsQuery failse.g. IntegrityErrorCode catches itcarries onTxn abortedin PostgreSQLNext query failscommands ignoredThe real error is the first failed query; this message is only what came after.
  1. Look further up the logs or Sentry breadcrumbs for the first error in the same request or task.
  2. Wrap queries that are allowed to fail in their own transaction.atomic() block, which creates a savepoint.
  3. Do not catch DatabaseError or IntegrityError and continue without a savepoint.
  4. In tests, use TestCase with atomic blocks around expected failures, or TransactionTestCase.
  5. With ATOMIC_REQUESTS = True, remember the whole view is one transaction.

Step-by-step solution

1. Find the first failure

Enable PostgreSQL's statement logging for errors, which is on by default:

grep -B2 "current transaction is aborted" /var/log/postgresql/postgresql-17-main.log | head

The line before the first "current transaction is aborted" in a session shows the original error, such as duplicate key value violates unique constraint or invalid input syntax for type uuid. In Sentry, breadcrumbs for the event list the earlier failing query.

2. Recognize the pattern in code

The classic version is "try to insert, fall back on conflict":

# broken inside any transaction
try:
    Tag.objects.create(name=name)
except IntegrityError:
    pass
tag = Tag.objects.get(name=name)   # InternalError: current transaction is aborted

If this runs inside ATOMIC_REQUESTS, a Celery task wrapped in atomic(), or a test case, the failed INSERT aborted the outer transaction and the get() is rejected.

what happens after the IntegrityErrortry/except only✕whole transaction aborted✕next query rejected✕error blamed on wrong lineinner atomic() savepoint✓only the savepoint rolls back✓outer transaction continues✓later queries workA savepoint limits the damage of an expected failuredjango-deployment.com
Wrap only the statement that may fail, not the code that follows it.

3. Use a savepoint around expected failures

from django.db import IntegrityError, transaction

try:
    with transaction.atomic():
        Tag.objects.create(name=name)
except IntegrityError:
    pass
tag = Tag.objects.get(name=name)

The inner atomic() becomes a savepoint inside the outer transaction. When the insert fails, Django rolls back to the savepoint, and the outer transaction stays usable. Better still, avoid the exception: Tag.objects.get_or_create(name=name) already uses a savepoint, and PostgreSQL's ON CONFLICT via bulk_create(..., ignore_conflicts=True) never raises.

4. Check long-running workers

A Celery task or management command that catches all exceptions in a loop can poison the connection for every following iteration:

for row in rows:
    try:
        with transaction.atomic():
            import_row(row)
    except Exception:
        logger.exception("row failed: %s", row["id"])

Putting the atomic() inside the loop means each row has its own transaction or savepoint, so one bad row does not abort the rest.

5. Fix tests

Django's TestCase wraps each test in a transaction. A test that expects an IntegrityError and then queries again fails with this message. Use the assertion inside its own atomic block:

def test_duplicate_email_rejected(self):
    with self.assertRaises(IntegrityError):
        with transaction.atomic():
            User.objects.create(email="a@example.com")
    self.assertEqual(User.objects.count(), 1)
outer transactions to be aware ofOuter transactionSafe patternATOMIC_REQUESTS viewwhole requestinner atomic()Task in atomic()whole taskatomic() per itemTestCase testwhole testatomic() in assertRaisesSignal handlercaller’s transactionatomic() around riskIn each case one failed query poisons the rest of the outer blockdjango-deployment.com
Autocommit mode only applies when no outer atomic block is active.

Explanation

PostgreSQL treats a transaction as all-or-nothing. After an error, the server marks the transaction as failed and refuses further commands, so it cannot commit a half-done unit of work by accident. Only ROLLBACK, or ROLLBACK TO SAVEPOINT if a savepoint exists, clears the state. Django's atomic() creates a savepoint when nested and rolls back to it on exception, which is exactly the tool for expected failures. MySQL and SQLite do not abort the whole transaction on most errors, which is why code that "worked" there breaks on PostgreSQL.

Edge cases / notes

  • Connection reuse. If a process catches the error and never rolls back outside any atomic block, Django's request cleanup resets the connection at the end of the request. Long-running scripts must handle it themselves.
  • Raw SQL. cursor.execute() failures abort the transaction the same way. Wrap them in atomic() too.
  • Duplicate keys from sequences. If the first error is a duplicate primary key, the sequence may be out of sync; see Fix PostgreSQL Sequence Out of Sync.

Next steps

FAQ

Why is the error on a query that looks correct?

Because the transaction was already aborted by an earlier failed query. The reported query is just the next one to run.

Does this happen with MySQL?

Mostly no. MySQL rolls back only the failed statement in most cases. PostgreSQL aborts the whole transaction, which is stricter.

Should I turn off ATOMIC_REQUESTS?

Not because of this error. Keep it if you rely on it, and use savepoints around statements that are expected to fail.

How do I recover the connection in a script?

Call transaction.rollback() when outside an atomic block, or structure the script so each unit of work uses its own atomic().

Keep going