I have two similar TSQL scripts where I have SET XACT_ABORT ON. I expect _test table to NOT EXIST after script execution as I am intentionally using a statement wrapped in a transaction that will raise an error.
The first script correctly rolls back the creation of _test table.
SET XACT_ABORT ON
GO
BEGIN TRANSACTION
CREATE TABLE dbo._test(ID int IDENTITY(1, 1) NOT NULL)
EXEC sp_does_not_exist --raises error!
GO
IF @@TRANCOUNT > 0
COMMIT;
However, the second script does not roll back the creation of _test and the table exists after the script execution:
SET XACT_ABORT ON
GO
BEGIN TRANSACTION
CREATE TABLE dbo._test(ID int IDENTITY(1, 1) NOT NULL)
EXEC sp_recompile sp_does_not_exist; --raises error!
GO
IF @@TRANCOUNT > 0
COMMIT;
Why does the second script not remove the _test table after execution?
You haven’t mentioned your SQL Server version, but since the only difference in your script is
sp_recompile, that seems like a good place to look. In 2008R2 it has the following logic:So
sp_recompilechecks for the existence of the object before it tries to access it directly, and if it isn’t found it raises an error with severity -1. The documentation forRAISERRORstates that a severity level less than zero is interpreted as zero, and the documentation for severity levels states that no system error is raised on severity zero.Indeed, adding the
RAISERRORfromsp_recompileinto your script shows that it doesn’t affect@@TRANCOUNT:@@TRANCOUNTis 1 before and after the error is raised, so there is nothing to trigger a rollback. But if you do this, the secondSELECTis never executed, because the error has been raised ‘directly’ by the database engine: