Reputation: 701
I am implementing many SSB working on two different instances. They are data push pattern based on asynchronous triggers.
My SQL Info is as shown below: Microsoft SQL Server Management Studio 10.50.2500.0 Microsoft Analysis Services Client Tools 10.50.2500.0 Microsoft Data Access Components (MDAC) 6.1.7601.17514 Microsoft MSXML 3.0 4.0 5.0 6.0 Microsoft Internet Explorer 9.0.8112.16421 Microsoft .NET Framework 2.0.50727.5448 Operating System 6.1.7601
My scenarios are mainly as shown below
Multiple Records are inserted as Bulk in table or One Record.
This data is sent to another DB.
The activation procedure starts between BEGIN TRAN and END TRAN.
It validates this message.
If validation not succeeded, this message should be removed from Queue and ACk is sent back to infrom that message was invalid using a different SSB objects.
Else, ACK is sent infroming message is read successfully.
Then, Activation Procedure call another Procedure to process the message body.
This USP_Process_Records is between BEGIN TRAN AND END TRAN too.
For so many reasons, this procedure might fail according to some buisness needs I've.
Either it'll Pro SQL Server 2008 Service Broker.
So in the Activation Procedure, it'll either go into failure condition for the USP_Process_Records or go to BEGIN CATCH part and rollback the transaction and send failure ACK.
At the end, I found that the previous read success ack isnt' sent at all and the second is sent normally.
So I am very confused for Transaction Management in Broker Service.
Should I use use BEGIN TRAN for each separated task and remove it from Receive and Process UPS?
Should I use TRY, CATCH inside USP_Process_Records too and return errors to USP_Receive_Records?
Should I modify my TRY, CATCH blocks ar Receive to avoid this issues
At the end, I want All acks to be sent even if something went wrong after and want to avoid Poison messages and rolling back at all. Thanks in advance.
-BTW I've used rusanu blog for Broker Service Error Handling and Read Pro SQL Server 2008 Service Broker Transaction Management part.
Find below sample for USP.
--USP_Receive_Records
BEGIN TRY
BEGIN TRAN
WHILE 1=1
BEGIN
SELECT @ReplyMessage = NULL, @TargetDlgHandle = NULL
WAITFOR (RECEIVE TOP(1)
@TargetDlgHandle=Conversation_Handle
,@ReplyMessage = CAST(message_body AS XML)
,@ReplyMessageName = Message_Type_Name
FROM Q_Service_Receive), TIMEOUT 1000
IF @TargetDlgHandle IS NULL
BREAK
--Check if the message has the same message type expected
IF @ReplyMessageName=N'Service_Msg'
BEGIN
--Send Batch Read Success ACK
--Send ACK Here
EXEC [dbo].[USP_ACKMsg_Send] @ACKMsg, @Service_Msg;
--Handle ACK Send failed!
-- Execute the USP_Service_Msg_Process for the batch rows
EXECUTE USP_Service_Msg_Process @ReplyMessageName, @RC OUTPUT;
--Case Processing Succeeded
IF @RC=0
BEGIN
--Send Batch Read Success ACK
END
--SEND ACK Processing failed with Return Code to define cause of the error
ELSE
BEGIN
--Send Batch Processing Failed ACK
END
END
END CONVERSATION @TargetDlgHandle;
END
COMMIT TRAN;
END TRY
BEGIN CATCH
if (XACT_STATE()) = -1
BEGIN
rollback transaction;
END;
if (XACT_STATE()) = 1
BEGIN
DECLARE @error int, @message nvarchar(4000), @handle uniqueidentifier;
SELECT @error = ERROR_NUMBER(), @message = ERROR_MESSAGE();
END conversation @handle with error = @error description = @message;
COMMIT;
END
END CATCH
END
--USP_Process_Records
BEGIN TRAN
While(@nCount <= @nodesCount)
BEGIN
IF(@S_HIS_Status = '02')
BEGIN
-- check N_Paid_Trans_ID is not nuul or zero or empty
IF( @N_GET_ID IS NULL OR @N_GET_ID = 0 OR @N_GET_ID = '')
BEGIN
SET @RC = 8
RETURN;
END
EXECUTE USP_Handle_Delivered_Service @N_GET_ID, @RC OUTPUT
SELECT @myERROR = @@ERROR--, @myRowCount = @@ROWCOUNT
IF @myERROR <> 0 OR @RC <> 0
BEGIN
ROLLBACK TRAN
END
END
--A lot of similar cases
END TRAN
Upvotes: 1
Views: 1204
Reputation: 294177
You are mixing BEGIN TRY/BEGIN CATCH blocks with old style @@ERROR checks. It makes both error handling and transaction handling pretty much impossible to manage. Consider this snippet of code:
SELECT @myERROR = @@ERROR--, @myRowCount = @@ROWCOUNT
IF @myERROR <> 0 OR @RC <> 0
BEGIN
ROLLBACK TRAN
END
Can you follow the control flow and the transaction flow involved here? The code is executing in the context of being called from a TRY/CATCH block, so the @@ERROR case should never occur and the control flow should jump to the CATCH block. But wait, what if the procedure is called from a different context when there is no TRY/CATCH block? then the @@ERROR case can be taken but that implies the control flow continues! Even when a TRY/CATCH contest is set up, if @RC is non-zero the transaction is rolled back but control flow continues to the next statements which will now execute in the context of per-statement standalone transactions since the overall encompassing transaction has rolled back! In other words in such case you may send an response Ack to a message you did not receive (you just rolled it back!). No wonder you are seeing cases when behavior seems erratic.
I recommend you stick with only one style of error handling (and the only sane style is BEGIN TRY/BEGIN CATCH blocks). Do not rollback intentionally in case of application logic error, but instead use RAISERROR
and rely on the CATCH block to rollback as necessary. Also do style your procedure after the template shown at Exception handling and nested transactions. This template allows for message-by-message decision to rollback to a safepoint in the transaction in case of error (ie. commit your RECEIVE batch of messages, even if some of the messages occurred an error in processing).
Upvotes: 3