I have an issue with an AFTER INSERT UPDATE TRIGGER. Test code below. The problem is that when a transaction on this table is started it's rolled back.
After a lot of research online, it seems that everyone is in aggreance that that is NOT the case. Regardless, is there something on server side setting I'm missing?
I have also added the sp_settriggerorder() to the first trigger, naming the one in question last.
I've also noted that disabling the below trigger allows the transaction to complete.
Severity below 11 is not an option because it prompts an ugly warning in the client application to forces the user to "Expand" the selection.
Severity 11 though 16 all give me this issue.
USE [Test]
GO
/****** Object: Trigger [dbo].[co_bln_AfterIup] Script Date: 06/29/2017 14:43:12 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[co_bln_AfterIup]
ON [dbo].[co_bln]
AFTER INSERT,UPDATE
AS
IF 1 = 1
RAISERROR('test error',16,1)
Short answer
1# Set SET XACT_ABORT OFF at the beginning of trigger execution
2# Do some DMLs(inserts/updates/deletes)
3# Set condition to fire RAISERROR with the message and severity under 18
4# Results would be committed transaction with raised error
Longer answer
There are two ways of handling errors in SQL Server ,one is using RAISERROR and another is using THROW. Which one you prefer would depend on a further explanation
Using throw you cannot specify severity, where as using RAISERROR you can
Using a higher(above 18) severity you can kill a user connection, which is not something you can do with throw
Also RAISERROR can be specified within explicit transaction (BEGIN/END) whereas
THROW cannot(Have to use TRY/CATCH block).
Whats important here is something that connects these two error handlers is XACT_ABORT
By Specifying XACT_ABORT you are defining a transaction behavior
It is possible within a single transaction to have certain transaction that will fail, and some of them wont, depending on XACT_ABORT whether is ON or OFF , some of these succeeded transaction might be executed while others wont, which is usually something you dont want to do if you want to keep consistent database
More about XACT_ABORT you can read here
Now how is this related to your error handlers?
If you specify at the beginning of transaction SET XACT_ABORT OFF, your RAISERROR error handler wont rollback transaction, and whatever it is that you was doing before will be reflected to the database.
You have to options to prevent this , using explicit ROLLBACK transaction or Enabling XACT_ABORT
If you want to use TRY/CATCH block, and do some error handling you can use either one of these. Whenever you call RAISERROR or THROW you will be directly transferred to the CATCH block. However remember that even if you are in the catch block if XACT_ABORT is OFF and you are using RAISERROR with no ROLLBACK command, that transaction will be completed regardless.
If you use THROW it will be rolledback immediately
However XACT_ABORT on or off has no effect on THROW handler and will be executed as expected( with rolling back all the changes)
Note also that even with XACT_ABORT OFF you can still rollback transaction using severity over 18
Therefore depending on what you are trying to achieve you can pick the one that suits you the best. Following the standard, THROW is the one to go, and its newer but if you wanted to just display the warning - back to Short answer
You could surround the statements around the RAISERROR in a TRY/CATCH block if you don't want the RAISERROR to end the transaction. Or you could surround the INSERT/UPDATE statement that fires the trigger with a TRY/CATCH block.
From RAISERROR documentation (Remarks):
When RAISERROR is run with a severity of 11 or higher in a TRY block, it transfers control to the associated CATCH block.
RAISERROR with a severity between 11 and 20 should transfer control to the catch block and should not necessarily abort the transaction if you don't write is as such.
Without seeing all your code, I'm guessing that some part of your program is rolling the transaction back as a response to the error raised in the trigger.
Related
I currently have an audit process that is part of all procedures and it is very basic at a high level.
Procedure is executed.
It inserts into the Audit table with a start time and status of Running.
It completes with out errors and then updates that record to show a status of Complete.
If there is an actual error it will update with a status of Error.
However if a user manually aborts the procedures via the SQL Server Management Studio no update will occur. The record will remain as "Running".
Is there any functionality I can use to capture this scenario so I can tie out the audit record?
You simply need to enclose it inside a transaction, and use XACT_ABORT for automatic rollback.
CREATE OR ALTER PROCEDURE YourProc
-- parameters here
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRAN;
-- everything else here
COMMIT;
Always use SET XACT_ABORT ON if you have a transaction. This ensures that if the code is aborted or it errors, the transaction will be rolled back and not left hanging.
Do not use TRY CATCH unless you want to actually handle the error and log it. It is not necessary to use CATCH to ensure a rollback if you have XACT_ABORT. If you do catch, then rethrow the error using THROW; rather than RAISERROR. For example:
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK;
INSERT Audit... ;
THROW;
END CATCH;
But note that certain errors cannot be caught, and a user abort cannot be caught either. I suggest you use XEvents for that .
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 some TSQL code with the following pattern:
BEGIN TRAN;
DECLARE #Set int;
BEGIN TRY
SET #Set = dbo.UDFThatCanTriggerAnError('ByCastingTheDescriptiveErrorToInt');
END TRY
BEGIN CATCH
SET #Set = -1; --Default value;
END CATCH
--Really in another sproc called from here but shown here for brevity
SAVE TRAN Testing;
ROLLBACK TRAN Testing;
--Back to the original sproc
ROLLBACK TRAN;
(In reality the rollbacks are only triggered if there are further errors inside the sprocs, but hopefully that gives the idea.)
In testing on SQL Server 2008 R2 SP1, the transaction save operation consistently throws the error:
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
If I change the UDF call to one which doesn't raise the error, or replace the line with a different way of setting the variable, the code completes.
It'd be so much easier if TRY..CATCH blocks here could be made to work in the same way the analogous blocks do in .Net and let me elect to reset the error flag, rather than automatically assuming every last error is terminal - this one definitely isn't, and is much less code than any alternative way of controlling flow.
So - is there a way to reset the error status inside the Catch block or do I need to rewrite the logic to avoid the caught error being thrown in the first place?
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