PROBLEM SUMMARY:
i made error handling that seems to be way too complicated and still does not solve all situations (as there can be situations where transaction gets in uncommitable state). I suspect i:
- have missed something important and doing it wrong (can you explain what? and how should i do it then?).
- haven’t missed anything- just have to accept that error handling is still huge problem in SQL Server.
Can you offer better solution (for described situation below)?
ABOUT MY SITUATION:
I have (couple of) stored procedure in SQL Server, that is called from different places. Can generalize for 2 situations:
- Procedure is called from .NET code, transaction is made and handled in SQL procedure
- Procedure is called in other procedure (to be more specific- in Service Broker activation procedure), so the transaction is handled by outer procedure.
I made it so, that procedure returns result (1 for success, 0 for failure) + returns message for logging purposes in case of error.
Inside the procedure:
- Set XACT_ABORT ON; — transaction not to be made uncommitable because of triggers.
- Declare @PartOfTran bit = 0; — is used, to save status: 1- if this procedure is part of other transaction or 0- should start new transaction.
- If this is part of other tran, then make save point. If not- then begin transaction.
- Begin try block- do everything and if there is no mistakes AND if this is not nested transaction do commit. If it is nested transaction- commit will be made in caller procedure.
- In case of error: if this is nested transaction and transaction is in commitable state- can do rollback to savepoint “MyTran”. if its not part of transaction, rollback transaction called “MyTran”. In all other cases- just return error code and message.
Code looks like this:
Create Procedure dbo.usp_MyProcedure
(
-- params here ...
@ReturnCode int out, -- 1 Success, != 1 Error
@ReturnMsg nvarchar(2048) out
)
AS
Begin
Set NoCount ON;
Set XACT_ABORT ON;
Declare @PartOfTran bit = 0;
IF(@@TRANCOUNT > 0)
Begin
SET @PartOfTran = 1;
SAVE TRAN MyTran;
END
Else
BEGIN TRAN MyTran;
Begin Try
-- insert table1
-- update table2
-- ....
IF(@PartOfTran = 0)
COMMIT TRAN MyTran;
Select @ReturnCode = 1, @ReturnMsg = Null;
End Try
Begin Catch
IF (XACT_STATE() = 1 And @PartOfTran = 1) OR @PartOfTran = 0
Rollback Tran MyTran;
Select @ReturnCode = 0, @ReturnMsg = ERROR_MESSAGE();
End Catch
End
OTHER LITERATURE:
From my favorite bloggers have seen:
- sommarskog – but i don’t like that “outer_sp” has line “IF @@trancount > 0 ROLLBACK TRANSACTION”, because in my case- outer procedure can be called in transaction, so in that case i have “Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.”
- rusanu – actually almost the same as i wrote here (maybe idea comes from that blog post- i wrote my solution based on all i have read about this subject). This blog post still does not solve what should i do with uncommitable transactions. This is problem in case of Service Broker. How can i make correct logging of error message, if i have to rollback uncommitable transaction? i have ideas about this, but all of them seems like workarounds not elegant solutions.
You won’t be able to achieve a solution that rolls back only the work done in
usp_MyProcedurein any condition. Consider the most obvious example: deadlock. When your are notified of exception 1205 (you’ve been chosen as a deadlock victim) the transaction has already rolled back ( in order to allow progress). As error handling goes, the only safe option is to further raise and re-throw so that the caller has a chance to react. The ‘uncommittable transaction’ is just a variation on that theme: there is just no way error handling can recover from such a situation in a manner that is sensible for the caller, when the caller has started a transaction. The best thing is to raise (re-throw). This is why I used the pattern you’ve seen in my blog at Exception HAndling and Nested TransactionsConsidering this in Service Broker context it means that there is no completely bullet proof, exception safe message handling routine. If you hit an uncommitable transaction (or a transaction that has already rolled back by the time you process the catch block, like 1205 deadlock) then your whole batch of received messages will have to rollback. Logging is usualy done in such situations after the outermost catch block (usually locate din the activated procedure). Here is pseudo code of how this would work: