I have a stored procedure which is called inside a trigger on Insert/Update/Delete.
The problem is that there is a certain code block inside this SP which is not critical.
Hence I want to ignore any erros arising from this code block.
I inserted this code block inside a TRY CATCH block. But to my surprise I got the following error:
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Then I tried using SAVE & ROLLBACK TRANSACTION along with TRY CATCH, that too failed with the following error:
The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
My server version is: Microsoft SQL Server 2008 (SP2) - 10.0.4279.0 (X64)
Sample DDL:
IF OBJECT_ID('TestTrigger') IS NOT NULL
DROP TRIGGER TestTrigger
GO
IF OBJECT_ID('TestProcedure') IS NOT NULL
DROP PROCEDURE TestProcedure
GO
IF OBJECT_ID('TestTable') IS NOT NULL
DROP TABLE TestTable
GO
CREATE TABLE TestTable (Data VARCHAR(20))
GO
CREATE PROC TestProcedure
AS
BEGIN
SAVE TRANSACTION Fallback
BEGIN TRY
DECLARE #a INT = 1/0
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION Fallback
END CATCH
END
GO
CREATE TRIGGER TestTrigger
ON TestTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
EXEC TestProcedure
END
GO
Code to replicate the error:
BEGIN TRANSACTION
INSERT INTO TestTable VALUES('data')
IF ##ERROR > 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
GO
I was going through the same torment, and I just solved it!!!
Just add this single line at the very first step of your TRIGGER and you're going to be fine:
SET XACT_ABORT OFF;
In my case, I'm handling the error feeding a specific table with the batch that caused the error and the error variables from SQL.
Default value for XACT_ABORT is ON, so the entire transaction won't be commited even if you're handling the error inside a TRY CATCH block (just as I'm doing). Setting its value for OFF will cause the transaction to be commited even when an error occurs.
However, I didn't test it when the error is not handled...
For more info:
SET XACT_ABORT (Transact-SQL) | Microsoft Docs
I'd suggest re-architecting this so that you don't poison the original transaction - maybe have the transaction send a service broker message (or just insert relevant data into some form of queue table), so that the "non-critical" part can take place in a completely independent transaction.
E.g. your trigger becomes:
CREATE TRIGGER TestTrigger
ON TestTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
INSERT INTO QueueTable (Col1,Col2)
SELECT COALESCE(i.Col1,d.Col1),COALESCE(i.Col2,d.Col2) from inserted i,deleted d
END
GO
You shouldn't do anything inside a trigger that might fail, unless you do want to force the transaction that initiated the trigger action to also fail.
This is a very similar question to Why try catch does not suppress exception in trigger
Also see the answer here T-SQL try catch transaction in trigger
I don’t think you can use savepoints inside a trigger. I mean, you can but I googled about it and I saw a few people saying that they don’t work. If you replace your “save transaction” for a begin transaction, it compiles. Of course it is not necessary because you have the outer transaction control and the inner rollback would rollback everything.
Related
I'm using an after update trigger on a table purely for testing purposes in order to force an error with an SSIS package. It's basically just a trigger that calls RAISERROR() with a static message after an update happens.
This works ok in most instances. However, when I call a specific stored procedure that contains a try/catch (no explicit transactions involved), and it updates the table, the update doesn't happen or is getting rolled back somehow. My understanding of Try/Catch was that it would not rollback unless you explicitly implemented BEGIN/COMMIT/ROLLBACK TRANSACTION.
I seem to be misunderstanding Try/Catch, or I'm misunderstanding how triggers function. I normally try not to use triggers, but for this use-case it made sense.
If I comment out the Try/Catch, everything functions as I'd expect.
CREATE PROCEDURE dbo.MySproc
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
UPDATE dbo.MyTableToFireTrigger SET SomeColumn = 1 WHERE SomeColumn = 0
END TRY
BEGIN CATCH
--I'm able to log the after update trigger error message here, but it seems to be rolling back the update.
END CATCH;
END
Trigger create:
CREATE TRIGGER dbo.MyTrigger ON dbo.MyTableToCauseTrigger
AFTER UPDATE
AS
BEGIN
RAISERROR('Error', 16, 1);
END;
In a transaction if an error occurred that transaction will be rolled back. This refers to the transaction concept. When you call your stored procedure a transaction is started. Then it causes your trigger to be fired and the error raised. Automatically the transaction will be rolled back.
Using SQL Server 2014:
I am going through the following article that includes useful patterns for TSQL error handling:
https://msdn.microsoft.com/en-IN/library/ms175976.aspx
I like to log errors so later on I can query, monitor, track and inspect the errors took place in my application's store procedures.
I was thinking to create a table and insert the error details as a row into the table in the CATCH block; however I am concern this might not be a good pattern OR there might be a built-in SQL server feature that can log the errors generated by the ;THROW statement.
What would be the best way to log the errors?
Update 1
I should mention that I always set XACT_ABORT on top of my SPs:
SET XACT_ABORT, NOCOUNT ON
Is it safe to assume that there is no way to log errors when XACT_ABORT is ON?
Update 2
The SET XACT_ABORT ON is according to this post:
http://www.sommarskog.se/error_handling/Part1.html#jumpXACT_ABORT
Can xp_logevent be a better alternative than adding an error record to a log table?
You have to be very careful with logging from CATCH locks. First and foremost, you must check the XACT_STATE() and honor it. If xact_state is -1 (
'uncommittable transaction') you cannot do any transactional operation, so the INSERT fail. You must first rollback, then insert. But you cannot simply rollback, because you may be in xact_state 0 (no transaction) in which case rollback would fail. And if xact_state is 1, you are still in the original transaction, and your INSERT may still be rolled back later and you'll loose all track of this error ever occurring.
Another approach to consider is to generate a user defined profiler event using sp_trace_generateevent and have a system trace monitoring your user event ID. This works in any xact_state state and has the advantage of keeping the record even if the encompassing transaction will roll back later.
I should mention that I always set XACT_ABORT
Stop doing this. Read Exception handling and nested transactions for a good SP pattern vis-a-vis error handling and transactions.
Yes it is better.
If you want to store then try this.
declare #Error_msg_desc varchar(500)
,#Error_err_code int
,#Error_sev_num int
,#Error_proc_nm varchar(100)
,#Error_line_num int
begin try
select 1/0
end try
begin catch
select #Error_err_code = ERROR_NUMBER()
,#Error_msg_desc = ERROR_MESSAGE()
,#Error_sev_num = ERROR_SEVERITY()
,#Error_proc_nm = ERROR_PROCEDURE()
,#Error_line_num = ERROR_LINE()
--create SqlLog Table
--Insert into Log Table
Insert into Sqllog values(#Error_err_code,#Error_msg_desc,#Error_sev_num,#Error_proc_nm,#Error_line_num)
end catch
I have this piece of script:
Create Table AA (ID int identity(1,1), Col1 varchar(10))
Create Table BB (ID int identity(1,1), Col1 varchar(10))
GO
Create proc p6
as
insert into AA
(Col1)
Values('')
GO
Create Trigger [dbo].[TR_AA] on [dbo].[AA]
After insert
As
--Set XACT_Abort off
Select 1/0
GO
Begin Try
Begin Tran
Select ##TRANCOUNT
exec p6
Commit Tran
End Try
Begin Catch
insert into BB(Col1)Values('')
Select * from AA
--Select XACT_STATE()
Rollback Tran
End Catch
Select Count(*) from AA
GO
When I run this code i am getting this error:
The current transaction cannot be committed and cannot support
operations that write to the log file. Roll back the transaction.
I already know what causes this issue.
The example is just an example. But I have lots of Business logics inside the trigger that I can't move them out.
So one workaround would be to put Set XACT_Abort off at the beginning of
the trigger. However, by doing that we override the default behaviour of SQL dealing with errors in triggers.
My question is if I do that does it expose any issue to the system?
Any other solution except for stripping the trigger off logic would be greatly appreciated.
If you want to keep your transaction alive, XACT_ABORT = OFF should help.
However setting XACT_ABORT = OFF doesn't guarantee that the transaction continues in all cases. It is dependent on severity of the error.
When SET XACT_ABORT is OFF, in some cases only the Transact-SQL statement that raised the error is rolled back and the transaction continues processing. Depending upon the severity of the error, the entire transaction may be rolled back even when SET XACT_ABORT is OFF.
Another issue with XACT_ABORT = OFF is that now your error handling and the process of data which is persisted is different across code with different XACT_ABORT setting.
EDIT
These links may help.
Why TRY CATCH does not suppress exception in trigger
Ignoring errors in Trigger
USE AdventureWorks;
GO
BEGIN TRANSACTION;
GO
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 10;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 11;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 12;
GO
COMMIT TRANSACTION;
GO
What happens if the first delete statement fails? Will the 2nd and 3rd delete statements be executed? The example doesn't have any error handling, will it leave an open transaction in the case of an exception, or will SQL Server rollback the transaction automatically? Open transaction = locked resources, right?
I am deciding whether I must apply TRY...CATCH to stored procedures that use transactions.
I am aware about set xact_abort on, but want to know what happens without it.
Here is what I found in docs - Controlling Transactions (Database Engine):
If an error prevents the successful completion of a transaction, SQL Server automatically rolls back the transaction and frees all resources held by the transaction
However I read in other posts that automatic rollback is not fired.
In your example, without the use of SET XACT_ABORT ON, the transaction will continue and commit even if the first statement fails. In the text you quoted, the key words are if an error **prevents** the successful completion of a transaction, and a DELETE statement failing does not prevent the transaction from completing.
An example of an error that would cause an automatic rollback is if the connection to the database was severed in the middle of a transaction. Further down the MSDN article you referenced says:
If a run-time statement error (such as a constraint violation) occurs
in a batch, the default behavior in the Database Engine is to roll
back only the statement that generated the error. You can change this
behavior using the SET XACT_ABORT statement. After SET XACT_ABORT ON
is executed, any run-time statement error causes an automatic rollback
of the current transaction. Compile errors, such as syntax errors, are
not affected by SET XACT_ABORT.
It's always a good idea to use error handling to catch errors and rollback if needed.
I prefer to control the process manually:
BEGIN TRY
BEGIN TRAN
-- do work
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
RAISERROR (...)
END CATCH
GO
I'm trying to insert a duplicate value in to a primary key column which raises a primary key violation error.I want to log this error inside the catch block .
Code Block :-
SET XACT_ABORT OFF
BEGIN TRY
BEGIN TRAN
INSERT INTO #Calender values (9,'Oct')
INSERT INTO #Calender values (2,'Unknown')
COMMIT TRAN
END TRY
BEGIN CATCH
Insert into #LogError values (1,'Error while inserting a duplicate value')
if ##TRANCOUNT >0
rollback tran
raiserror('Error while inserting a duplicate value ',16,20)
END CATCH
when i execute the above code it prints out the custom error message which is displayed in the catch block but doesn't insert the value in to the #LogError table
Error while inserting a duplicate value
But when i use SET XACT_ABORT ON i get a different error message but still it doesn't inserts the error message into the table
The current transaction cannot be committed and cannot support operations
that write to the log file. Roll back the transaction.
My question is
1.How to log error into the table
2.Why do i get different error message when i set xact_ABORT on .Is it a good practice to set XACT_ABORT on before every transaction
It does insert the record into #LogError but then you rollback the transaction which removes it.
You need to do the insert after the rollback or insert into a table variable instead (that are not affected by the rollback).
When an error is encountered in the try block this can leave your transaction in a doomed state. You should test the value of XACT_STATE() (see example c in the TRY ... CATCH topic) in the catch block to check for this before doing anything that writes to the log or trying to commit.
When XACT_ABORT is on any error of severity > 10 in a try block will have this effect.
As SqlServer doesn't support Autonomous transaction (nested and independent transaction), it's not possible (in fact, you can, under some condition, use CLR SP with custom connectstring - doing it's own, non local, connection) to use a database table to log SP execution activity/error messages.
To fix, this missing functionnality, I've developed a toolbox (100% T-SQL) based on the use of XML parameter passed as reference (OUTPUT parameter) which is filled during SP execution and can be save into a dedicated database table at the end.
Disclamer: I'm a Iorga employee (cofounder) and I've developped the following LGPL v3 toolbox. My objective is not Iorga/self promotion but sharing knowledge and simplify T-SQL developper life.
See, teach, enhance as you wish SPLogger
Today (October 19th of 2015) I've just released the 1.3 including a Unit Test System based on SPLogger.