{"record":{"id":"ad3ee2dbbe50435f","repo":"doctrine/orm","slug":"commit-failed","errorCode":null,"errorMessage":"Commit failed","messagePattern":"Commit failed","errorType":"exception","errorClass":"OptimisticLockException","httpStatus":null,"severity":"critical","filePath":"src/UnitOfWork.php","lineNumber":446,"sourceCode":"            }\n\n            // Entity deletions come last. Their order only needs to take care of other deletions\n            // (first delete entities depending upon others, before deleting depended-upon entities).\n            if ($this->entityDeletions) {\n                $this->executeDeletions();\n            }\n\n            $commitFailed = false;\n            try {\n                if ($conn->commit() === false) {\n                    $commitFailed = true;\n                }\n            } catch (DBAL\\Exception $e) {\n                $commitFailed = true;\n            }\n\n            if ($commitFailed) {\n                throw new OptimisticLockException('Commit failed', null, $e ?? null);\n            }\n\n            $successful = true;\n        } finally {\n            if (! $successful) {\n                $this->em->close();\n\n                if ($conn->isTransactionActive()) {\n                    $conn->rollBack();\n                }\n\n                $this->afterTransactionRolledBack();\n            }\n        }\n\n        $this->afterTransactionComplete();\n\n        // Unset removed entities from collections, and take new snapshots from","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/UnitOfWork.php#L428-L464","documentation":"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.","triggerScenarios":"$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.","commonSituations":"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.","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."],"exampleFix":"// before\n$em->flush(); // OptimisticLockException: Commit failed, EntityManager is closed afterwards\n\n// after\nretry:\ntry {\n    $em->flush();\n} catch (OptimisticLockException $e) {\n    $code = $e->getPrevious()?->getCode();\n    if (in_array($code, ['1213', '1205', '40P01'], true) && $attempts++ < 3) {\n        usleep(50000 * $attempts); // backoff\n        $em = $this->managerRegistry->resetManager(); // fresh EntityManager, reload entities\n        goto retry;\n    }\n    throw $e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Commit failures are usually transient deadlocks: retry with a fresh EM at the use-case level\n$maxAttempts = 3;\nfor ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {\n    try {\n        $this->doUnitOfWork($input); // (re)load entities, mutate, flush\n        return;\n    } catch (OptimisticLockException $e) {\n        $driverCode = (string) $e->getPrevious()?->getCode();\n        $transient  = in_array($driverCode, ['1213', '1205', '40001', '40P01'], true);\n        if (! $transient || $attempt === $maxAttempts) {\n            throw $e;\n        }\n        $this->managerRegistry->resetManager(); // closed EM must be replaced\n        usleep(50000 * (2 ** $attempt));\n    }\n}","preventionTips":["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"],"tags":["doctrine-orm","unit-of-work","transaction","commit","deadlock","rollback","dbal","flush"],"backgroundTag":"transaction-commit-failed","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}