Some SQL Server errors can leave no active transaction for Laravel’s nested rollback to return to. A later savepoint error may then mask the database exception that actually caused the failure. Catch the error outside the transaction, preserve it, and discard the cached connection before subsequent work.
Last updated: November 14, 2026.
<?php
use IlluminateSupportFacadesDB;
use Throwable;
try {
DB::connection('sqlsrv')->transaction(function () {
DB::connection('sqlsrv')->transaction(function () {
// Database work that may fail and end the SQL Server transaction.
DB::connection('sqlsrv')->statement('EXEC dbo.ProcessImport');
});
});
} catch (Throwable $error) {
// Do not let later requests reuse transaction state from this connection.
DB::purge('sqlsrv');
throw $error;
}The outer catch is deliberately outside both transaction closures. Purging removes Laravel’s cached connection; rethrowing keeps the exception and stack that reached this boundary.
Understand the mismatch
Laravel’s transaction documentation describes automatic rollback and rethrow when the closure fails. Nested transactions are commonly represented with savepoints. SQL Server’s SAVE TRANSACTION documentation explains that a savepoint rollback is not a full transaction rollback and also lists restrictions for uncommittable transactions.
If SQL Server has already ended the transaction, another rollback to Laravel’s savepoint can fail. Avoid catching the inner exception merely to continue on the same connection. Also avoid manually changing Laravel’s transaction counter; that can make framework state diverge further from the server.
Diagnose before retrying
During diagnosis, log the original SQLSTATE, driver error number, connection name, and transaction nesting level without logging query secrets. SQL Server’s XACT_STATE documentation distinguishes an active committable transaction, an uncommittable transaction, and no active user transaction.
Do not automatically retry an operation unless it is idempotent and the failure class is known to be transient. A stored procedure or trigger may have performed work before the error. The current report used Laravel 13.34.0, SQL Server 2022, and pdo_sqlsrv 5.13.3; confirm behavior against your installed versions because framework transaction handling can change.