Based on the Books Online documentation of SET XACT_ABORT ON, i get the impression that if a T-SQL statement raises a run-time error, the entire transaction is terminated and rolled back:
Remarks
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a run-time error, the entire transaction is terminated and rolled back.
Testing this in SQL Server 2008 R2:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
PRINT 'TranCount befor an error = '+CAST(##Trancount AS varchar(50))
DROP TABLE QuertyAsdf
PRINT 'TranCount after an error = '+CAST(##Trancount AS varchar(50))
Gives the output:
TranCount befor an error = 1
Msg 3701, Level 11, State 5, Line 6
Cannot drop the table 'QwertyAsdf', because it does not exist or you do not have permission.
TranCount after an error = 1
i was also under the impression that SET XACT_ABORT ON terminates the batch if there's an error:
SET XACT_ABORT ON instructs SQL Server to rollback the entire transaction and abort the batch when a run-time error occurs.
That sounds handy. How can i make it do that too?
The SQL Server only rollback transactions when Severity level greater or equals 16.
See example:
Msg 544, Level 16, State 1, Line 1
Cannot insert explicit value for identity column in table 'ORC_ORCAMENTO' whenIDENTITY_INSERT is set to OFF.
Test on SQL Server 2008 R2
SET XACT_ABORT ON;
BEGIN TRANSACTION;
PRINT 'TranCount befor an error = '+CAST(##Trancount AS varchar(50))
insert into ORC_ORCAMENTO (ORCID, ORCNOME, ORCATIVO) VALUES (1, 'TESTE_ALEXP', 0);
PRINT 'TranCount after an error = '+CAST(##Trancount AS varchar(50))
Returns
TranCount befor an error = 1
Msg 544, Level 16, State 1, Line 5
Cannot insert explicit value for identity column in table 'ORC_ORCAMENTO' when IDENTITY_INSERT is set to OFF.
TranCount after an error = 0
See Microsoft Error Message Levels on
https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors
When you use xact abort on, in the try catch statement, you can manually raise an error to make the transaction roll back.
set xact_abort on;
begin try
...dml statements here....
if conditions here...
raiseerror(....);
end try
begin catch
end catch
Related
I have two procedures, one outer procedure and one inner procedure, where I would like to understand the behaviour of the error handling. The inner procedure provokes an error and is trying to insert something in the catch block into a table. After that the error is raised, passed to the outer procedure and then should roll back the transaction.
I'm trying to understand why my code is throwing the error message:
Msg 50000, Level 11, State 1, Procedure dbo.OuterProcedure, Line 21 [Batch Start Line 9]
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
I would expect the following message:
Msg 50000, Level 11, State 1, Procedure dbo.OuterProcedure, Line 21 [Batch Start Line 9]
Error converting data type varchar to numeric.
I know that the issue comes from the catch block in the inner procedure and it happens because I'm trying to insert something into my log table before raising the error. When I switch those statements or delete the insert, I get the actual error message. I also know that it is not smart to do the logging in the inner procedure and inside a transaction that is rolled back anyways.
I would like to understand what is making this transaction a "doomed" transaction even though the XACT_ABORT is set to off.
Full code:
My main procedure:
CREATE PROCEDURE [dbo].[OuterProcedure]
AS
BEGIN
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRANSACTION ;
-- do other stuff
EXEC [dbo].[innerprocedure];
-- do other stuff
COMMIT TRANSACTION ;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE #ErrText NVARCHAR(2000);
SET #ErrText = ISNULL(ERROR_MESSAGE(), 'nothing')
RAISERROR(#ErrText, 11, 1) WITH NOWAIT
END CATCH;
END;
My inner procedure:
CREATE PROCEDURE [dbo].[InnerProcedure]
AS
BEGIN
SET XACT_ABORT OFF;
SET NOCOUNT ON;
BEGIN TRY
-- do other stuff
-- provoke error
SELECT
CASE
WHEN 1 = 0
THEN 0.0
ELSE ''
END;
-- do other stuff
END TRY
BEGIN CATCH
DECLARE #ErrText NVARCHAR(2000);
SELECT
#ErrText = ISNULL(ERROR_MESSAGE(), 'nothing');
INSERT INTO [dbo].[logtable]
(
[Message]
, [ErrNr]
)
VALUES
( #ErrText
, -1
);
RAISERROR(#LogText, 11, 0) WITH NOWAIT;
END CATCH;
END;
I would like to understand what is making this transaction a "doomed"
transaction even though the XACT_ABORT is set to off.
XACT_STATE() is -1 in the catch block so the transaction is doomed.
SELECT
CASE
WHEN 1 = 0
THEN 0.0
ELSE ''
END;
Throws error
Error converting data type varchar to numeric.
"Most conversion errors" is one of the error types that Erland Sommarskog puts in the category of errors.
Batch Abortion with Rollback This is the strongest reaction SQL Server
can take to a user error. These are errors that abort execution on the
spot if there is no CATCH handler on the stack and they also roll back
any open transaction. If there is a CATCH handler, the error is
caught, but any open transaction is doomed and must be rolled back.
The behaviour is the same, no matter whether XACT_ABORT is ON or OFF.
The categorisation of error behaviours is somewhat cryptic, undocumented and not intuitive. Read his article for more details.
Recently, I encountered an SQL error due to the following statement in our legacy code. It tries to drop a temp table, and move on if it hasn't been defined. Clearly, it's a bad way to check the existence of a temp table, but that's not my question here.
BEGIN TRY
DROP TABLE #my_temp_table
END TRY
BEGIN CATCH
END CATCH
The statement runs fine (without any error) as is, but as soon as you put it in a Begin Tran/Commit Tran block like the following, the behavior becomes interesting.
BEGIN TRAN
BEGIN TRY
DROP TABLE #my_temp_table
END TRY
BEGIN CATCH
END CATCH
COMMIT TRAN
My understanding is that Try..Catch block doesn't affect Transactions - once it goes into the Catch block, the transaction will be in an uncommittable state, and the transaction will be rolled back, and that's what I see on SQL Server 2008 R2 (SP1) - 10.50.2550.0. When it's executed inside a Begin Tran/Commit Tran block, we will get an error:
Msg 3930, Level 16, State 1, Line 8
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.
However, it runs without any error on SQL Server 2012 - 11.0.5058.0. The XACT_STATE() returns 1 after the END CATCH line. The transaction will be committed, and if there are other data changes before and after the DROP TABLE statement, the changes will stay.
BEGIN TRAN
BEGIN TRY
DROP TABLE #my_temp_table
END TRY
BEGIN CATCH
END CATCH
PRINT XACT_STATE()
COMMIT TRAN
In all these tests, I've made sure XACT_ABORT is OFF. So my question is what will cause this behavior difference. Is it truly a different between 2008 R2 and 2012, or it's some server/DB settings controlling how Try...Catch block and Transaction work.
Edit 1: I tried to run the following script on both 2008 R2 and 2012 instances. I also tried putting the INSERT dbo.UserOptionsLog line in different places, before the Begin Tran, after the Begin Try, after the Begin Catch, after the the commit tran, but it doesn't change the results.
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'UserOptionsLog')
CREATE TABLE dbo.UserOptionsLog([Set Option] SYSNAME, [Value] VARCHAR(100), ID INT IDENTITY NOT NULL)
BEGIN TRAN
DELETE FROM dbo.UserOptionsLog
INSERT dbo.UserOptionsLog EXEC('DBCC USEROPTIONS')
SELECT * FROM dbo.UserOptionsLog
BEGIN TRY
DROP TABLE #my_temp_table
END TRY
BEGIN CATCH
END CATCH
COMMIT TRAN
Results from 2008 R2 instance.
Set Option Value ID
textsize 2147483647 40
language us_english 41
dateformat mdy 42
datefirst 7 43
lock_timeout -1 44
quoted_identifier SET 45
arithabort SET 46
ansi_null_dflt_on SET 47
ansi_warnings SET 48
ansi_padding SET 49
ansi_nulls SET 50
concat_null_yields_null SET 51
isolation level read committed 52
Messages from 2008 R2 instance
(0 row(s) affected)
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
(13 row(s) affected)
(13 row(s) affected)
Msg 3930, Level 16, State 1, Line 18
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.
Results from 2012 instance.
Set Option Value ID
textsize 2147483647 53
language us_english 54
dateformat mdy 55
datefirst 7 56
lock_timeout -1 57
quoted_identifier SET 58
arithabort SET 59
ansi_null_dflt_on SET 60
ansi_warnings SET 61
ansi_padding SET 62
ansi_nulls SET 63
concat_null_yields_null SET 64
isolation level read committed 65
Messages from 2012 instance.
(13 row(s) affected)
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
(13 row(s) affected)
(13 row(s) affected)
[...]and that's what I see on SQL Server 2008 R2 (SP1) - 10.50.2550.0.
When it's executed inside a Begin Tran/Commit Tran block, we will get
an error:
Msg 3930, Level 16, State 1, Line 8 The current transaction cannot be
committed and cannot support operations that write to the log file.
Roll back the transaction. Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The
transaction is rolled back. However, it runs without any error on SQL
Server 2012 - 11.0.5058.0. [...]
I believe the reason for this diff. behavior is the value of XACT_ABORT setting.
When OFF then XACT_STATE() returns 1, TX is commitable and COMMIT TRAN is executing without errors:
SET XACT_ABORT OFF
BEGIN TRAN
BEGIN TRY
DROP TABLE #my_temp_table
END TRY
BEGIN CATCH
END CATCH
PRINT XACT_STATE()
COMMIT TRAN
but when is ON
SET XACT_ABORT ON
BEGIN TRAN
BEGIN TRY
DROP TABLE #my_temp_table
END TRY
BEGIN CATCH
END CATCH
PRINT XACT_STATE()
COMMIT TRAN
because of error/exception intercepted by CATCH block, TX becomes domed/uncommitable (-1) and COMMIT TRAN raise another error/exception:
-1
Msg 3930, Level 16, State 1, Line 10
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Update:
I reproduced reported behaviour on SQL2008R2. On 2008R2, it seems that no matter what value has XACT_ABORT (ON/OFF), TX becomes uncommitable. SQL2012 changed this behaviour thus: TX becomes uncommitable only when XACT_ABORT is ON.
Perhaps I am missing something, but even though the RAISERRORs below have severity of 16 (as per documentation) the transaction is still committed as if XACT_ABORT ON has no effect.
CREATE PROCEDURE [ExploringGroups].[RemoveMember]
#groupId uniqueidentifier,
#adminUsername nvarchar(50),
#targetUsername nvarchar(50)
AS
SET XACT_ABORT ON
BEGIN TRANSACTION
DECLARE
#adminUserId uniqueidentifier = dbo.fn_userId(#adminUsername),
#targetUserId uniqueidentifier = dbo.fn_userId(#targetUsername)
IF #targetUserId IS NULL OR ExploringGroups.IsMember(#groupId, #targetUserId) = 0
RAISERROR('Target user was not located', 16, 1)
IF ExploringGroups.IsInRole(#groupId, #adminUserId, 'adm') = 0
RAISERROR('Specified user is not an administrator of this group', 16, 2)
IF #adminUserId = #targetUserId
RAISERROR('You cannot remove yourself', 16, 3)
-- statements below still execute and commit even though there was an error raised above
DELETE FROM ExploringGroups.MemberRole WHERE GroupId = #groupId AND UserId = #targetUserId
DELETE FROM ExploringGroups.Membership WHERE GroupId = #groupId AND UserId = #targetUserId
COMMIT
RETURN 0
Calling
exec exploringgroups.removemember '356048C5-BAB3-45C9-BE3C-A7227225DFDD', 'Crypton', 'Crypton'
Produces
Msg 50000, Level 16, State 2, Procedure RemoveMember, Line 20
Specified user is not an administrator of this group
Msg 50000, Level 16, State 3, Procedure RemoveMember, Line 24
You cannot remove yourself
I thought XACT_ABORT was supposed to roll back the whole transaction if it's set to ON?
Actually, it behaves exactly as it was supposed to. XACT_ABORT really caused the transaction to roll back, so were there any data modifications up to the point of the error they will be rolled back. However, it didn't affect the flow of execution, and it didn't stop running the stored procedure, so the following two DELETEs were executed as implicite transactions. Explicit RAISERRORs don't abort the batch.
See this simplified version:
create table #t(i int);
insert #t values(1);
go
alter procedure sp
as
set xact_abort on
begin tran
raiserror ('x', 16, 1);
print 'deleting';
delete #t;
commit;
go
exec sp
go
select * from #t
go
The only funny thing was that the error about COMMIT not having a corresponding BEGIN TRAN was swallowed.
With SEH, it would jump into CATCH block.
You should be using a "THROW" statement versus the RAISERROR approach.
Use a BEGIN TRY and BEGIN CATCH and commit the transaction normally or rollback in the CATCH block.
BEGIN TRY
-- Do your inserts or throw error
-- Commit transaction
END TRY
BEGIN CATCH
-- Rollback
END CATCH;
If I exec this batch:
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
drop table dbo.tblPrueba
select * from dbo.tblPrueba
PRINT 'finish'
rollback transaction
The ouput is this:
start
Msg 8134, Level 16, State 1, Line 3
Divide by zero error encountered.
continue
Msg 208, Level 16, State 1, Line 6
Invalid object name 'dbo.tblPrueba'.
I am forcing two errors:
- the first one: PRINT 1/0 (that generates this error:
Msg 8134, Level 16, State 1, Line 3
Divide by zero error encountered.
) And continue executing the batch
- the second one:
drop table dbo.tblPrueba
select * from dbo.tblPrueba
That generates this error:
Msg 208, Level 16, State 1, Line 6
Invalid object name 'dbo.tblPrueba'.
And stops execution of the batch
What is the different between them? Where can I learn those that stop execution and those that doesn´t?
Thanks a lot!!
Since the first error is a divide by zero error, this behavior depends on your ARITHABORT, ARITHIGNORE and ANSI_WARNINGS settings.
From the article:
These three SET commands give you very fine-grained control for a very
small set of errors. When a division by zero or an overflow occurs,
there are no less four choices.
No action at all, result is NULL – when ARITHIGNORE is ON.
Warning message, result is NULL – when all are OFF.
Statement-termination – when ANSI_WARNINGS is ON.
Batch-abortion – when ARITHABORT is ON and ANSI_WARNINGS is OFF.
As far as which errors stop execution and which ones don't, please refer to the same article.
The easiest way to ensure all errors are handled correctly is to use TRY/CATCH
Without this, different errors can be statement, scope or batch aborting depending on settings such as ARITHxx, ANSI_WARNINGS and XACT_ABORT. This is demonstrated and discussed at "Error Handling in SQL 2000"
You can see the different (no SET options changed) with this
CREATE TABLE dbo.tblPrueba (gbn int);
GO
BEGIN TRY
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
drop table dbo.tblPrueba
select * from dbo.tblPrueba
PRINT 'finish'
rollback transaction
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE();
IF XACT_STATE() <> 0 rollback transaction
END CATCH
If I run this twice, I get this because the DROP is never executed
Msg 2714, Level 16, State 6, Line 1
There is already an object named 'tblPrueba' in the database.
Where can I learn those that stop execution
You can use exception handling
Begin try
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
create table #t
(
id int
)
drop table #t
select * from #t
PRINT 'finish'
rollback transaction
End Try
Begin Catch
if( XACT_STATE() == 1)
Rollback Tran
End Catch
You can use Set XACT_ABORT ON like below.
Set XACT_ABORT ON
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
create table #t
(
id int
)
drop table #t
select * from #t
PRINT 'finish'
rollback transaction
Currently I have a large import process that I'm trying to wrap inside a transaction so if anything breaks - i could rollback. The issue I have is that when the TSQL inside the trans blows up, it won't rollback when the following SQL error occurs
Msg 8152, Level 16, State 14, Line 249
String or binary data would be truncated.
The statement has been terminated.
The below wraps this import TSQL
DECLARE #error INT
SELECT #error = 0
BEGIN TRANSACTION
--** begin import TSQL
--** end import TSQL
SELECT #error = ##error
IF #error != 0 GOTO handle_error
COMMIT
handle_error:
IF #error != 0
BEGIN
ROLLBACK
END
If your on SQL 2005 you can try:
BEGIN TRANSACTION
BEGIN TRY
--Run your Statements
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
DECLARE #Msg NVARCHAR(MAX)
SELECT #Msg=ERROR_MESSAGE()
RAISERROR('Error Occured: %s', 20, 101,#msg) WITH LOG
END CATCH
How about turning on xact_abort
set xact_abort on
I would also point out that if you are receiving this error often, you need to revise the size of the column you are entering data into or adjust your cleaning process to prep the data before putting it into the prod table. In SSIS, You could also have the data that deosn't meet the standard size go to a bad data table and process the rest.