doctrine/orm · critical · OptimisticLockException
Commit failed
Error message
Commit failed
What it means
UnitOfWork::commit() runs the flush inside a DB transaction; if the final COMMIT fails - the driver throws a DBAL exception (deadlock, lock wait timeout, deferred constraint violation, dropped connection) or commit() returns false - Doctrine marks the commit failed, closes the EntityManager, attempts a rollback in the finally block, and rethrows as OptimisticLockException('Commit failed') with the original driver error attached as previous. The OptimisticLockException wrapper is a legacy choice: the failure is normally a database-level commit problem, not an ORM version conflict.
Source
Thrown at src/UnitOfWork.php:446
}
// Entity deletions come last. Their order only needs to take care of other deletions
// (first delete entities depending upon others, before deleting depended-upon entities).
if ($this->entityDeletions) {
$this->executeDeletions();
}
$commitFailed = false;
try {
if ($conn->commit() === false) {
$commitFailed = true;
}
} catch (DBAL\Exception $e) {
$commitFailed = true;
}
if ($commitFailed) {
throw new OptimisticLockException('Commit failed', null, $e ?? null);
}
$successful = true;
} finally {
if (! $successful) {
$this->em->close();
if ($conn->isTransactionActive()) {
$conn->rollBack();
}
$this->afterTransactionRolledBack();
}
}
$this->afterTransactionComplete();
// Unset removed entities from collections, and take new snapshots fromView on GitHub (pinned to d9b9ff7301)
Solutions
- Inspect the previous exception ($e->getPrevious()) to get the real driver error code before deciding what to do.
- For deadlock/lock-wait causes (MySQL 1213/1205, PostgreSQL 40P01) retry the whole unit of work with a fresh EntityManager and backoff - data must be re-read, so retry at the use-case level.
- Shorten transactions: flush in smaller batches, move slow/non-DB work outside the transaction.
- For PostgreSQL deferred-constraint failures the data itself is wrong - fix the violating rows; retrying will fail identically.
Example fix
// before
$em->flush(); // OptimisticLockException: Commit failed, EntityManager is closed afterwards
// after
retry:
try {
$em->flush();
} catch (OptimisticLockException $e) {
$code = $e->getPrevious()?->getCode();
if (in_array($code, ['1213', '1205', '40P01'], true) && $attempts++ < 3) {
usleep(50000 * $attempts); // backoff
$em = $this->managerRegistry->resetManager(); // fresh EntityManager, reload entities
goto retry;
}
throw $e;
} Defensive patterns
Strategy: retry
Try / catch
// Commit failures are usually transient deadlocks: retry with a fresh EM at the use-case level
$maxAttempts = 3;
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
try {
$this->doUnitOfWork($input); // (re)load entities, mutate, flush
return;
} catch (OptimisticLockException $e) {
$driverCode = (string) $e->getPrevious()?->getCode();
$transient = in_array($driverCode, ['1213', '1205', '40001', '40P01'], true);
if (! $transient || $attempt === $maxAttempts) {
throw $e;
}
$this->managerRegistry->resetManager(); // closed EM must be replaced
usleep(50000 * (2 ** $attempt));
}
} Prevention
- Keep transactions short: flush in modest batches, no slow I/O inside the transaction
- Access rows in a consistent order across workers to reduce deadlocks
- Always log OptimisticLockException::getPrevious() - the driver code tells you whether to retry or fix data
- Watch for DEFERRABLE INITIALLY DEFERRED constraints on PostgreSQL; they fail at COMMIT, not at INSERT
When it happens
Trigger: $em->flush() whose COMMIT statement fails: MySQL/InnoDB deadlock (1213) or innodb_lock_wait_timeout (1205) hitting at commit; PostgreSQL deferred foreign-key/unique constraints failing at COMMIT; the server dropping the connection mid-transaction (wait_timeout, network blip); or a driver/setup where commit() returns false without throwing.
Common situations: Concurrent workers updating overlapping rows under load; long-running transactions; PostgreSQL schemas using DEFERRABLE INITIALLY DEFERRED constraints; batch imports racing each other; cloud databases killing idle connections.
Related errors
- Cannot call recomputeSingleEntityChangeSet before computeCha
- Dirty entity can not be scheduled for insertion.
- Unexpected entity state: %s. %s
- No persister found for entity.
- Uninitialized result set mapping.
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/ad3ee2dbbe50435f.
Report an issue: GitHub.