Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
Author |
Topic |
Joker
Starting Member
1 Post |
Posted - 2010-03-24 : 04:31:40
|
HiAm trying to code correct and are wondering which of these code blocks are the most right. Thanks--Version 1:BEGIN TRY BEGIN TRANSACTION DELETE FROM dbo.Address WHERE UserId = @userid; IF(@@ROWCOUNT = 0) BEGIN IF(@@TRANCOUNT > 0) ROLLBACK TRANSACTION; SET @statuscode = 20; RETURN; END SET @statuscode = 100; COMMIT TRANSACTION;END TRYBEGIN CATCH IF(@@TRANCOUNT > 0)ROLLBACK TRANSACTION; EXEC dbo.Error_ErrorHandler; SET @statuscode = 99;END CATCH --Version 2:BEGIN TRY SET NOCOUNT ON SET XACT_ABORT ON DELETE FROM dbo.Address WHERE UserId = @userid; IF(@@ROWCOUNT = 0) BEGIN IF(@@TRANCOUNT > 0) ROLLBACK TRANSACTION; SET @statuscode = 20; RETURN; END SET @statuscode = 100;END TRYBEGIN CATCH IF (XACT_STATE() = -1) ROLLBACK TRANSACTION; ELSE IF (XACT_STATE() = 1) COMMIT TRANSACTION; EXEC dbo.Error_ErrorHandler; SET @statuscode = 99;END CATCH --Version 3:BEGIN TRY SET NOCOUNT ON SET XACT_ABORT ON BEGIN TRANSACTION DELETE FROM dbo.Address WHERE UserId = @userid; IF(@@ROWCOUNT = 0) BEGIN IF(@@TRANCOUNT > 0) ROLLBACK TRANSACTION; SET @statuscode = 20; RETURN; END SET @statuscode = 100; COMMIT TRANSACTION;END TRYBEGIN CATCH IF(@@TRANCOUNT > 0) ROLLBACK TRANSACTION; EXEC dbo.Error_ErrorHandler; SET @statuscode = 99;END CATCH --Version 4:BEGIN TRY SET NOCOUNT ON SET XACT_ABORT ON BEGIN TRANSACTION DELETE FROM dbo.Address WHERE UserId = @userid; IF(@@ROWCOUNT = 0) BEGIN IF(@@TRANCOUNT > 0) ROLLBACK TRANSACTION; SET @statuscode = 20; RETURN; END SET @statuscode = 100; COMMIT TRANSACTION;END TRYBEGIN CATCH IF (XACT_STATE() = -1) ROLLBACK TRANSACTION; ELSE IF (XACT_STATE() = 1) COMMIT TRANSACTION; EXEC dbo.Error_ErrorHandler; SET @statuscode = 99;END CATCH |
|
Kristen
Test
22859 Posts |
Posted - 2010-03-24 : 06:37:26
|
I'll be interested in answers too.Here's how I do a TRANSACTION that I want to ROLLBACK without impacting anyone that called me (I also return an error code so the caller can Rollback their changes too, if they need to.BEGIN TRANSACTION MyLabel_01SAVE TRANSACTION MyLabel_02... some code ...IF ... test for No Errors ...BEGIN COMMIT TRANSACTION MyLabel_01ENDELSEBEGIN ROLLBACK TRANSACTION MyLabel_02 COMMIT TRANSACTION MyLabel_01END We putSET NOCOUNT ONSET XACT_ABORT ONSET ARITHABORT ON at the top of all our SProcs (After AS and before the first statement in the Sproc) |
 |
|
DBA in the making
Aged Yak Warrior
638 Posts |
Posted - 2010-03-24 : 07:32:32
|
Definitely not version 2, which appears to be missing a BEGIN TRANSACTION. :)There are 10 types of people in the world, those that understand binary, and those that don't. |
 |
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2010-03-24 : 08:13:18
|
Similar to Kristen'sDECLARE @tranCounter INT SET @tranCounter = @@TRANCOUNT-- Set up a transaction so we can undo if anything goes wrong!-- If we are inside a transaction save the transaction so we can-- rollback only to this point. If we aren't in a transaction create oneIF @tranCounter > 0 SAVE TRANSACTION addAdminUserELSE BEGIN TRANSACTION AddAdminUser< STUFF >-- Do something that can result in an error and test for it.IF @error <> 0 GOTO failWithError< STUFF >-- If we get to this point then all is good. Commit and exit-- If in a nested transaction just save the changes to the transaction-- If inside our own transaction then commit that transactionIF @tranCounter > 0 SAVE TRANSACTION AddAdminUserELSE COMMIT TRANSACTION AddAdminUserRETURN-- If we ever get here then something has gone wrong!-- rollback the saved transaction and exit as gracefully as possible!failWithError:SET @msgText = ' ! ERROR : Error Occurred, return code (' + CAST(@error AS VARCHAR(10)) + ')'RAISERROR(@msgText, 11, 11) WITH NOWAITROLLBACK TRANSACTION AddAdminUserRETURN @error Depending on the stored proc then parts will be in TRY CATCH blocks and there won't be RAISERROR messages.Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
Kristen
Test
22859 Posts |
Posted - 2010-03-24 : 12:58:15
|
TC does that work when you ROLLBACK TRANSACTION AddAdminUser?I seemed to think that in my experiments any ROLLBACK caused the outer callers to have problems, whereas if we COMMITTED, and then returned, it was fine - hence we created a dummy "wrapper transaction", then a named inner transaction within that, and we either Committed or Rolled back the inner transaction, but we always Committed the "wrapper transaction" which left us in a "correct state for the outer callerYears since we built that logic into our systems, so I now can't remember what the experimentation was at the time!! |
 |
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2010-03-24 : 13:06:51
|
It seems to work anyway -- I tested it fairly hard. The only way the ROLLBACK can happen is if there has been a problem and it will either ROLLBACK a transaction started by the stored proc (if it was called outside a TRANSACTIOn) or a save point. The following example script works for me!BEGIN TRANSACTIONCREATE TABLE #foo ([val] VARCHAR(15))INSERT #foo SELECT 'ALIVE!'SELECT @@TRANCOUNTDECLARE @tranCounter INTSET @tranCounter = @@TRANCOUNTIF @tranCounter > 0 SAVE TRANSACTION fooINSERT #foo SELECT 'I''LL DIE!!!'SELECT @@TRANCOUNTSELECT * FROM #fooROLLBACK TRANSACTION fooSELECT @@TRANCOUNTSELECT * FROM #fooROLLBACK TRANSACTIONSELECT @@TRANCOUNT Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
Kristen
Test
22859 Posts |
Posted - 2010-03-24 : 13:17:35
|
Indeed, seems fine  CREATE PROCEDURE TranTest02 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @tranCounter INT, @error INT, @msgText NVarchar(4000)PRINT 'TranTest02 Start' SELECT @tranCounter = @@TRANCOUNT, @error = 0 -- Assume no error -- Set up a transaction so we can undo if anything goes wrong! -- If we are inside a transaction save the transaction so we can -- rollback only to this point. If we aren't in a transaction create one IF @tranCounter > 0 SAVE TRANSACTION addAdminUser ELSE BEGIN TRANSACTION AddAdminUser -- Do STUFF here, but for this test we will force an error: IF @TheReturnValue <> 0 BEGIN SET @error = 1 END -- Do something that can result in an error and test for it. IF @error <> 0 GOTO failWithError -- Do more STUFF here - this is unrechable in this test -- If we get to this point then all is good. Commit and exit -- If in a nested transaction just save the changes to the transaction -- If inside our own transaction then commit that transaction IF @tranCounter > 0 BEGIN SAVE TRANSACTION AddAdminUser END ELSE BEGIN COMMIT TRANSACTION AddAdminUser ENDPRINT 'TranTest02 RETURN 0' RETURN 0 -- If we ever get here then something has gone wrong! -- rollback the saved transaction and exit as gracefully as possible!failWithError: SET @msgText = ' ! ERROR : Error Occurred, return code (' + CAST(@error AS VARCHAR(10)) + ')' RAISERROR(@msgText, 11, 11) WITH NOWAIT ROLLBACK TRANSACTION AddAdminUserPRINT 'TranTest02 RETURN ERROR' RETURN @errorGOCREATE PROCEDURE TranTest01 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INTPRINT 'TranTest01 Start' BEGIN TRANSACTION EXEC @error = TranTest02 @TheReturnValue = @TheReturnValue COMMITPRINT 'TranTest01 End' RETURN @errorGODECLARE @intResult intSET NOCOUNT ONPRINT 'Test OK Start'EXEC @intResult = TranTest01 @TheReturnValue = 0SELECT [@intResult] = @intResultPRINT 'Test OK End'SET NOCOUNT OFFSET NOCOUNT ONPRINT 'Test Fail Start'EXEC @intResult = TranTest01 @TheReturnValue = 1SELECT [@intResult] = @intResultPRINT 'Test Fail End'SET NOCOUNT OFFDROP PROCEDURE TranTest02GODROP PROCEDURE TranTest01GO |
 |
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2010-03-24 : 13:20:13
|
Maybe it was a quirk in the way 2000 handled transactions?I've not got a 2000 box around any more to test on. I implemented this framework only after we moved to 2005 in an effort to clean up the error handling code a little.Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
Kristen
Test
22859 Posts |
Posted - 2010-03-24 : 13:25:16
|
Kristen way:CREATE PROCEDURE TranTest04 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INT, @msgText NVarchar(4000)PRINT 'TranTest04 Start' SELECT @error = 0 -- Assume no error BEGIN TRANSACTION MyLabel_01 SAVE TRANSACTION MyLabel_02 -- Do STUFF here, but for this test we will force an error: IF @TheReturnValue <> 0 BEGIN SET @error = 1 END -- Do something that can result in an error and test for it. IF @error <> 0 GOTO failWithError -- Do more STUFF here - this is unrechable in this test failWithError: IF @error = 0 BEGIN -- No error COMMIT TRANSACTION MyLabel_01 END ELSE BEGIN -- Error occurred SET @msgText = ' ! ERROR : Error Occurred, return code (' + CAST(@error AS VARCHAR(10)) + ')' RAISERROR(@msgText, 11, 11) WITH NOWAIT ROLLBACK TRANSACTION MyLabel_02 COMMIT TRANSACTION MyLabel_01 ENDPRINT 'TranTest04 RETURN 0' RETURN @errorGOCREATE PROCEDURE TranTest03 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INTPRINT 'TranTest03 Start' BEGIN TRANSACTION EXEC @error = TranTest04 @TheReturnValue = @TheReturnValue COMMITPRINT 'TranTest03 End' RETURN @errorGODECLARE @intResult intSET NOCOUNT ONPRINT 'Test OK Start'EXEC @intResult = TranTest03 @TheReturnValue = 0SELECT [@intResult] = @intResultPRINT 'Test OK End'SET NOCOUNT OFFSET NOCOUNT ONPRINT 'Test Fail Start'EXEC @intResult = TranTest03 @TheReturnValue = 1SELECT [@intResult] = @intResultPRINT 'Test Fail End'SET NOCOUNT OFFDROP PROCEDURE TranTest04GODROP PROCEDURE TranTest03GO |
 |
|
Kristen
Test
22859 Posts |
Posted - 2010-03-24 : 13:28:24
|
and the "simple" way with no SAVE POINT (which gives run-time error)CREATE PROCEDURE TranTest06 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INT, @msgText NVarchar(4000)PRINT 'TranTest06 Start' SELECT @error = 0 -- Assume no error BEGIN TRANSACTION -- Do STUFF here, but for this test we will force an error: IF @TheReturnValue <> 0 BEGIN SET @error = 1 END -- Do something that can result in an error and test for it. IF @error <> 0 GOTO failWithError -- Do more STUFF here - this is unrechable in this test failWithError: IF @error = 0 BEGIN -- No error COMMIT TRANSACTION END ELSE BEGIN -- Error occurred SET @msgText = ' ! ERROR : Error Occurred, return code (' + CAST(@error AS VARCHAR(10)) + ')' RAISERROR(@msgText, 11, 11) WITH NOWAIT ROLLBACK TRANSACTION ENDPRINT 'TranTest06 RETURN 0' RETURN @errorGOCREATE PROCEDURE TranTest05 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INTPRINT 'TranTest05 Start' BEGIN TRANSACTION EXEC @error = TranTest06 @TheReturnValue = @TheReturnValue COMMITPRINT 'TranTest05 End' RETURN @errorGODECLARE @intResult intSET NOCOUNT ONPRINT 'Test OK Start'EXEC @intResult = TranTest05 @TheReturnValue = 0SELECT [@intResult] = @intResultPRINT 'Test OK End'SET NOCOUNT OFFSET NOCOUNT ONPRINT 'Test Fail Start'EXEC @intResult = TranTest05 @TheReturnValue = 1SELECT [@intResult] = @intResultPRINT 'Test Fail End'SET NOCOUNT OFFDROP PROCEDURE TranTest06GODROP PROCEDURE TranTest05GOTest OK StartTranTest05 StartTranTest06 StartTranTest06 RETURN 0TranTest05 End@intResult ----------- 0Test OK EndTest Fail StartServer: Msg 50000, Level 11, State 11, Procedure TranTest06, Line 37 ! ERROR : Error Occurred, return code (1)TranTest05 StartTranTest06 StartServer: Msg 266, Level 16, State 2, Procedure TranTest06, Line 0Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.Server: Msg 3902, Level 16, State 1, Procedure TranTest05, Line 11The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.TranTest06 RETURN 0TranTest05 End@intResult ----------- 1Test Fail End |
 |
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2010-03-24 : 13:40:56
|
Kristen! Don't do that to me at half five! Thought my way was broken from your script but it still works!CREATE PROCEDURE TranTest06 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INT, @msgText NVarchar(4000)PRINT 'TranTest06 Start' SELECT @error = 0 -- Assume no error DECLARE @transactions INT SET @transactions = @@TRANCOUNT IF @transactions > 0 SAVE TRANSACTION foo ELSE BEGIN TRANSACTION -- Do STUFF here, but for this test we will force an error: IF @TheReturnValue <> 0 BEGIN SET @error = 1 END -- Do something that can result in an error and test for it. IF @error <> 0 GOTO failWithError -- Do more STUFF here - this is unrechable in this test failWithError: IF @error = 0 BEGIN -- No error IF @transactions > 0 SAVE TRANSACTION foo ELSE COMMIT TRANSACTION END ELSE BEGIN -- Error occurred SET @msgText = ' ! ERROR : Error Occurred, return code (' + CAST(@error AS VARCHAR(10)) + ')' RAISERROR(@msgText, 11, 11) WITH NOWAIT SELECT @@TRANCOUNT IF @transactions > 0 ROLLBACK TRANSACTION foo ELSE ROLLBACK TRANSACTION ENDPRINT 'TranTest06 RETURN 0' RETURN @errorGOCREATE PROCEDURE TranTest05 @TheReturnValue int -- 1=Force error, 0=No errorASDECLARE @error INTPRINT 'TranTest05 Start' BEGIN TRANSACTION SELECT @@TRANCOUNT EXEC @error = TranTest06 @TheReturnValue = @TheReturnValue COMMITPRINT 'TranTest05 End' RETURN @errorGO--MAIN BODYDECLARE @intResult intSET NOCOUNT ONPRINT 'Test OK Start'EXEC @intResult = TranTest05 @TheReturnValue = 0SELECT [@intResult] = @intResultPRINT 'Test OK End'SET NOCOUNT OFFSET NOCOUNT ONPRINT 'Test Fail Start'EXEC @intResult = TranTest05 @TheReturnValue = 1SELECT [@intResult] = @intResultPRINT 'Test Fail End'SET NOCOUNT OFFDROP PROCEDURE TranTest06GODROP PROCEDURE TranTest05GO Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
Kristen
Test
22859 Posts |
Posted - 2010-03-24 : 13:43:24
|
" Thought my way was broken from your script but it still works!"I thought your way was broken because I got an error message - then realised it was your raiserror() !! |
 |
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2010-03-24 : 14:47:08
|
lolCharlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
Lamprey
Master Smack Fu Yak Hacker
4614 Posts |
Posted - 2010-03-24 : 15:13:10
|
quote: Originally posted by DBA in the making Definitely not version 2, which appears to be missing a BEGIN TRANSACTION. :)
Not to get to picky, just informational.. None of the transactions are needed (in the OP first post) since the single delete is atomic and thus already in a implicit transaction. |
 |
|
|
|
|
|
|