I've encountered this in SQL Server 2019 (v15).
This is a simplified version of much larger stored procedure. But this shows the behavior I am puzzled by, inside the exec there is a transaction, this transaction is then committed, then error is thrown.
I would expect that this change is persisted, but it's not, looks like the whole content of EXEC statement is reverted.
Can someone explain the behavior in this scenario?
Here is the code sample to reproduce this:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table (test int);
INSERT INTO my_table VALUES (1);
BEGIN TRY
SELECT 'start'
EXEC(' EXEC( ''
BEGIN TRANSACTION;
SET IMPLICIT_TRANSACTIONS OFF;
INSERT INTO my_table VALUES (2);
COMMIT;
THROW 1;
BEGIN TRANSACTION;
INSERT INTO my_table VALUES (3);
COMMIT;
'' )' )
SELECT 'after EXEC'
THROW
END TRY
BEGIN CATCH
EXEC ('ROLLBACK TRANSACTION;');
SELECT 'In CATCH Block'
END CATCH
SELECT 'After END CATCH'
SELECT * FROM my_table;
The problem is not the transaction, it is your T-SQL, it contains a syntax error. WE can see this if we expose the error within the
CATCHwith something likePRINT ERROR_MESSAGE(), which shows us the error inside theTRYis:A syntax error means that the entire batch is not run, and if the batch doesn't run, there is no transaction as it also never ran.
The syntax error is your
THROW 1;; you can'tTHROWan arbitrary error number. The syntax forTHROWis eitherTHROW;orTHROW <Error Number>, <Error Message>, <Error State>;; you must pass all the parameters, or none. If you are using no parameters, this is known as a "rethrow" and can only appear inside aCATCH.For your SQL to work as you want, you need to correct the syntax, and pass 3 parameters to
THROW. For example:This returns the results you expected:
It also produces the follow error:
This is expected, as the
THROWis after theCOMMITand before the nextBEGIN TRANSACTION, so there was no transaction to rollback. Though, moving theTHROWto after theBEGIN TRANSACTIONwill result in a different error.When dealing with tranasctions in deffered batches, like this, you need to handle the transactions in the same scope. Jumping out to the outer scope, and then creating a new inner scope to rollback a tranasction from a completely different scope won't work.