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
Related
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 a stored procedure which is runs automatically every morning in SQL Server 2008 R2, part of this stored procedure involves executing other stored procedures. The format can be summarised thus:
BEGIN TRY
-- Various SQL Commands
EXECUTE storedprocedure1
EXECUTE storedprocedure2
-- etc
END TRY
BEGIN CATCH
--This logs the error to a table
EXECUTE errortrappingprocedure
END CATCH
storedprocedure1 and storedprocedure2 basically truncate a table and select into it from another table. Something along the lines of:
BEGIN TRY
TRUNCATE Table1
INSERT INTO Table1 (A, B, C)
SELECT A, B, C FROM MainTable
END TRY
BEGIN CATCH
EXECUTE errortrappingprocedure
END CATCH
The error trapping procedure contains this:
INSERT INTO
[Internal].dbo.Error_Trapping
(
[Error_Number],
[Error_Severity],
[Error_State],
[Error_Procedure],
[Error_Line],
[Error_Message],
[Error_DateTime]
)
(
SELECT
ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE(),
GETDATE()
)
99% of the time this works, however occasionally we will find that storedprocedure1 hasn't completed, with Table1 only being part populated. However no errors are logged in our error table. I've tested the error trapping procedure and it does work.
When I later run storedprocedure1 manually it completes fine. No data in the source table will have changed by this point so it's obviously not a problem with the data, something else has happened in that instant which has caused the procedure to fail. Is there a better way for me to log errors here, or somewhere else within the database I can look to try and find out why it is failing?
Try to use SET ARITHABORT (see link). It must ROLLBACK in your case. Also the answer of #Kartic seem reasonable.
I recommned also to read about implicit and explicit transactions - I think that this is your problem. You have several implicit transactions and when error happeneds you are in the middle of the job - so only part is rollbackеd and you have some data in that tables.
There are some type of Errors that TRY..CATCH block will not handle them, look here for more information https://technet.microsoft.com/en-us/library/ms179296(v=sql.105).aspx . for such Errors you should handle them in your application.
also I think you might have transaction management problem in your application too.
I am not sure if I understood you completely. Below code is too big for comment. So posting as an answer for your reference. If this is not what you want, I'll delete it.
Can we add transaction handling part as well.
DECLARE #err_msg NVARCHAR(MAX)
BEGIN TRY
BEGIN TRAN
-- Your code goes here
COMMIT TRAN
END TRY
BEGIN CATCH
SET #err_msg = ERROR_MESSAGE()
SET #err_msg = REPLACE(#err_msg, '''', '''''')
ROLLBACK TRAN
-- Do something with #err_msg
END CATCH
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.
I put transactions in all my "set" procedures. No problems. Everything works.
In this case, I need one set procedure, to call another, thankfully, only once, or that would potentially complicate things further.
So the happy bath would be.
I'm in ProcA and start a transaction.
It calls ProcB and it starts a transaction.
ProcB is successful and commits.
ProcA is successful and commits.
However, what happens if ProcB fails, rollsback, and rethrows the error. It should cause ProcA to rollback as well correct?
What if ProcB succeeds, commits, then ProcA subsequently fails, and rollsback...will what happened in ProcB be rolled back? or is it commited?
I need these two to work together, either both succeed, or fail and both be rolled back. What's the best way to ensure this happens?
I'm working with Microsoft SQL Server 2008 R2 (SP1)
Note: If ProcB requires a transaction because it can be called without ProcA wrapping it. And technically, ProcA won't always call ProcB (depends on input).
Here's a simple demo to show what happens with nested transations:
CREATE TABLE TranTest (Field1 INTEGER)
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 1 open transaction
INSERT TranTest VALUES (1)
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2 open transactions
INSERT TranTest VALUES (2)
ROLLBACK TRANSACTION -- this rolls back ALL transaction
SELECT ##TRANCOUNT -- 0 open transactions (you may have expected 1?)
SELECT * FROM TranTest -- No rows
Instead f the ROLLBACK above, if you did a COMMIT TRANSACTION, this actual does nothing other then decrement ##TRANCOUNT. So you then would need to to either COMMIT the outer transaction (which would COMMIT both rows to the table), or do a ROLLBACK which would result in no rows being committed to the table.
Here's the MSDN ref on nested transactions: http://msdn.microsoft.com/en-us/library/ms189336.aspx
Just use XACT_ABORT ON, and you are all set. Run the following script and see for yourself:
CREATE DATABASE ak_test;
GO
USE ak_test;
GO
CREATE TABLE dbo.a(i INT CONSTRAINT a_CannotInsertNegative CHECK(i>=0));
GO
CREATE TABLE dbo.b(i INT CONSTRAINT b_CannotInsertNegative CHECK(i>=0));
GO
CREATE PROCEDURE dbo.innerProc #i INT
AS
SET XACT_ABORT ON ;
BEGIN TRAN
INSERT b(i)VALUES(#i);
COMMIT;
GO
CREATE PROCEDURE dbo.outerProc #i1 INT, #i2 INT, #i3 INT
AS
SET XACT_ABORT ON ;
BEGIN TRAN
INSERT a(i)VALUES(#i1);
EXEC innerProc #i=#i2;
INSERT a(i)VALUES(#i3);
COMMIT;
GO
-- succeeds
EXEC dbo.outerProc 1, 2, 3;
SELECT * FROM dbo.a;
SELECT * FROM dbo.b;
GO
-- inner proc fails
EXEC dbo.outerProc 2, -3, 4;
GO
SELECT * FROM dbo.a;
SELECT * FROM dbo.b;
GO
-- second insert in outer proc fails
EXEC dbo.outerProc 3, 4, -5;
GO
SELECT * FROM dbo.a;
SELECT * FROM dbo.b;
I'm paranoid about transactions (there was this transaction left open on Production once that no one noticed for half an hour...) so I'd warp the potentially inner transaction like so:
CREATE PROCEDURE etcetc
...
DECLARE #IsTransaction bit = 0
IF ##trancount > 0
BEGIN
BEGIN TRANSACTION
SET #IsTransaction = 1
END
...
IF #IsTransaction = 1
BEGIN
COMMIT
-- or ROLLBACk, as necessary
END
All transaction processing (and handling of errors that occur within the transaction) must then be dealt with at whatever level launched the transaction.
(And did anyone else notice how BOL doesn't actually say what happens when you issue a ROLLBACK to a named transaction that isn't the outermost transaction? They do spell out every other permutation...)
Here's a strange problem I'm running into on a production server. It has happened twice in the last two weeks, and this is a server that gets a lot of traffic.
We have some code in a Web Service that executes a BEGIN TRAN, then runs a few SQL queries (two inserts followed by an update). Then at the end executes a COMMIT. Twice now we have gotten the message in the logs:
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
Between the first two inserts and the update, we call another web service, so there could be a slight delay between the first two inserts and last update before the COMMIT is called. Could this be causing our problem? We're running this on IIS 7 and Server 2008 R2 (all updated applied).
Originally we though it could be the app pools getting recycled, but changed that to recycle in the middle of the night. Now I'm not sure what would be causing SQL server to forget the call to BEGIN TRAN.
This web service does get called quite a bit. Has anyone seen something like this before? I'm at a total loss at the moment...
Any help or suggestion appreciated greatly!
It looks like your transaction failed, got rolled back and there is nothing to commit
example of such a thing
CREATE TABLE BlaTest(id INT PRIMARY KEY NOT NULL)
GO
Now run this
BEGIN TRAN
INSERT BlaTest VALUES('a')
GO
COMMIT TRAN
Here is the error
Msg 245, Level 16, State 1, Line 3
Conversion failed when converting the varchar value 'a' to data type int.
Msg 3902, Level 16, State 1, Line 2
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
This will run without a problem
BEGIN TRAN
INSERT BlaTest VALUES(5)
GO
COMMIT TRAN
A good article on transactions is Error Handling in SQL 2005 and Later by Erland Sommarskog
My issue was I needed a BEGIN and END around my BEGIN TRAN and COMMIT TRAN.
BEGIN
BEGIN TRAN
INSERT BlaTest VALUES(5)
GO
COMMIT TRAN
END
BEGIN TRANS
at the top will help
I had the same issue. This is what I did to solve it.
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION
After I Checked the SQL Query and Add a BEGIN TRAN it will executed successfully. Here My sample code. It will work:
ALTER procedure [dbo].[DeactivateUser]
#UserId bigint,
#LoginEmail Nvarchar(100),
#merchantId int
as
Begin
Begin tran
update Users set
LoginEmail='inactive'+CONVERT(VARCHAR(11), getdate(), 106)+'-'+#LoginEmail,
IsActive=0
where LoginEmail=#LoginEmail and MerchantID=#merchantId
if(##ERROR=0)
begin
commit Tran
select 0
end
else
begin
rollback Tran
select -1
end
end