Roll back in SQL Server 2005 - sql-server

My question: Is it possible to rollback a stored procedure from another stored procedure in SQL Server 2005?
I have SP1 to insert the values into one table and SP2 to insert the values into another table.
So, if any error comes while executing the SP2 I want to rollback the SP1 also.
Please anyone help me to solve my problem.
Thanks,
Bharath

You need to wrap both calls in a single transaction.
if you call them in SQL then this is way, or using a more comprehensive version like the other answer by answered 7 mins ago gbn.
create proc doall as
BEGIN TRY
BEGIN TRAN
EXECUTE SP1
EXECUTE SP1
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH;
if you're calling the SPs from another source, such as from a non SQL program, you need to setup the outer transaction using a Microsoft Distributed Transaction Coordinator (MSDTC) service.
Depending on the API you're using you set up the transaction in Code, and then commit and rollback in code, dependant on conditions.
for example in .net you can use the System.Transactions namespace to create distributed transactions.
In the main program
var tran = new System.Transactions.Transaction();
.
.
.
in one piece of code doe a db call (and pass the tran object to the sql connection) so it enlists in the transaction... if it fails - abort the transaction (trans.Rollback())
.
.
.
.
in another piece of code do another db call (and pass the tran object to the sql connection) so it enlists in the transaction... if it fails - abort the transaction (trans.Rollback())
.
.
.
later...
if both pieces of code succeed commit the transaction
This is a good introduction to the this namespace if you're using .net

You need a wrapper stored procedure to manage the transaction.
##TRANCOUNT on entry and exit to a stored procedure must be the same, otherwise you get error 266. So you can't exit SP1 having started a TXN for example.
I assume that SP1 and SP2 ca nbe called standalone so you need a nested transaction.
Then you hit the same error because
BEGIN TRAN adds one to ##TRANCOUNT
COMMIT TRAN subtracts one from ##TRANCOUNT
ROLLBACK makes ##TRANCOUNT zero
So you can still get error 266.
My answer here explains more about it, including nesting, error 266 suppression etc: Nested stored procedures containing TRY CATCH ROLLBACK pattern?
So in your case, you need soemthing like this
CREATE PROCEDURE Wrapper
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0
BEGIN TRANSACTION
EXEC SP1
EXEC SP2
IF #starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
GO

Related

Stored procedure with multiple updates, insert and deletes. What happens if last DML command fails?

So, if I have multiple DML commands inside a stored procedure in SQL Server, if the last one fails, will all the other ones rollback? Considering I am not inside a transaction scope!
You need your stored procedure to use TRY CATCH and a TRANSACTION .
BEGIN TRAN
BEGIN TRY
COMMIT TRANSACTION
END TRY
BEGIN CATCH
-- if error, roll back any changes done by any of the SQL statements
ROLLBACK TRANSACTION
END CATCH
Refer to this
http://techfunda.com/howto/192/transaction-in-stored-procedure

Rolling back transactions within stored procedures in Transact-SQL

I am new to using Transact-SQL, and I have a question on how transactions within nested stored procedures would be handled.
Consider the following example, where we create an example table as follows:
CREATE TABLE EXAMPLE_TABLE
(
ID INT,
NAME VARCHAR(255)
);
Then, we create a stored procedure with no parameters. This stored procedure involves inserting values into the table from above.
CREATE PROCEDURE SP1
AS
BEGIN
BEGIN TRANSACTION
INSERT INTO EXAMPLE_TABLE (ID, NAME)
VALUES (1, 'BOB')
COMMIT TRANSACTION;
END;
And then we create a second stored procedure with one parameter that calls our first stored procedure.
CREATE PROCEDURE sp2
#EXAMPLE INT
AS
BEGIN
BEGIN TRANSACTION
EXEC SP1
IF (#EXAMPLE < 10)
ROLLBACK TRANSACTION;
ELSE
COMMIT TRANSACTION;
END;
And then we call our second stored procedure as follows:
EXEC sp2 #EXAMPLE = 5;
At the end of this execution, will the values have been added to the EXAMPLE_TABLE? Or does the rollback in the outer stored procedure mean that everything has been rolled back, and nothing committed?
Transactions are scoped, so anything within a transaction is committed/rolled back together. So a value of 5 on your #example variable would prevent records from being added to the EXAMPLE_TABLE. You can check this fiddle for a demo.
I will add that if this example is in anyway similar to actual code you'll be writing, I would suggest to just check the variable value and make a decision on whether or not to run the insert stored procedure in the first place.
The conclusion of Aaron's answer is correct, but the reasoning is a little misleading.
Transactions aren't really "scoped" in the usual way you would think of scoping. The outermost begin tran does of course begin a transaction. But any nested begin tran doesn't really do anything other than increment the ##trancount. Then, when you commit, this doesn't really commit anything unless ##trancount is 1. Only the outermost commit is a "real" commit. Finally, a rollback will rollback everything, not just the current, "most nested" transaction, returning ##trancount to 0. At that point, if you try to commit or rollback you will get an error:
begin tran
print ##trancount
begin tran
print ##trancount
rollback
print ##trancount
commit
1
2
0
Msg 3902, Level 16, State 1, Line 61
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
For this reason, as a stylistic guide when actually coding transactions, I strongly suggest not treating a begin tran as the start of a block which needs to be indented. Treat begin tran, commit and rollback as regular statements, not the start and end of blocks.
The only exception to this behaviour is when you begin a named transaction, in which case you can rollback to the start of that named transaction.

BEGIN TRY/CATCH and MSDTC error

1/ The following code snippet show me the expected error: The INSERT statement conflicted with the FOREIGN KEY constraint FK_...
SET XACT_ABORT ON;
BEGIN TRANSACTION
INSERT INTO linkedsrv1.db1.[dbo].tbl1 ([Col1], [Col2])
VALUES (1200, 0)
COMMIT TRANSACTION
2/ But when I put this in a BEGIN TRY/CATCH, the error message is vague: Msg 1206, Level 18, State 118, Line 18
The Microsoft Distributed Transaction Coordinator (MS DTC) has cancelled the distributed transaction.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION
-- Error is on this line
INSERT INTO linkedsrv1.db1.[dbo].tbl1 ([IdWebsite], [IdProductType])
VALUES (1200, 0)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Error' -- Code not reached
SELECT ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE()
IF XACT_STATE() != 0
ROLLBACK TRANSACTION
END CATCH
Any idea why this happens?
Later edit:
It works in case I remove the unneeded explicit transaction. It is still not clear why I get this error when I put BEGIN/COMMIT TRAN.
I get the same error in case I have multiple inserts in multiple tables situated on linked server.
Any comment / remark is welcomed.
From MSDN:
SYMPTOMS
Consider the following scenario. You use the SQL Native Client OLE DB provider (SQLNCLI) in SQL Server 2005 to create a linked server. You create a distributed transaction. The distributed transaction contains a query that uses the linked server to retrieve data from a table. When you commit the distributed transaction, you may receive the following error message:
Msg 1206, Level 18, State 167, Line 3
The Microsoft Distributed Transaction Coordinator (MS DTC) has cancelled
the distributed transaction.
Additionally, you may receive the following error message when you run a query after this behavior occurs:
Msg 8525, Level 16, State 1, Line 1
Distributed transaction completed. Either enlist this session in a new
transaction or the NULL transaction.
This problem occurs if the following conditions are true:
You use the SQLNCLI provider to create a linked server between two
instances of SQL Server 2005.
The XACT_ABORT option is set to ON.
In the distributed transaction, you try to release a rowset before
all rows in the rowset are processed.
Note This problem may also occur if you call the ReleaseRows method in a distributed transaction to release a rowset before you commit a distributed transaction in an application.
CAUSE
This problem occurs because the SQLNCLI provider incorrectly sends an attention signal to the linked server to roll back the distributed transaction.
WORKAROUND
To prevent the SQLNCLI provider from sending an attention signal to the server, use the SQLNCLI provider to consume fully any rowsets that the OLE DB consumer creates.
Update
you need to configure 'remote proc trans' to "1" in server parameters.
Ex:
exec sp_configure 'remote proc trans','1'
reconfigure with override
This will permmit you to execute any distributed queries.
More Update
If you are using .Net framework in front end too, then I think you can use
TransactionScope Class. Remove transaction from query and put the Transaction in code level.
I have went to through this pain!
If you are performing any CRUD operation on a single table TRANSACTION is not needed.
In this case, the problem is, XACT_STATE() returns -1 because there is an error in the active transaction. But, ROLLBACK TRANSACTION fails, since there is NO transactions happened. You did only one transaction, INSERT, which failed, so there are no other transactions to rollback.
Its always better to relay on ##TRANCOUNT than XACT_STATE() (at least in this case).
to make it work, change like this(though I don't support TRAN for single table):
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
BEGIN TRANSACTION starts a distributed transaction between the server running the statements and the linked server, since potentially you can run updates against both servers. When the INSERT fails it needs to cancel the distributed transaction, thus the error you are getting. So you have to handle errors on two levels (insert and transaction). In this scenario, you'll need two TRY/CATCH blocks as follows:
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION
BEGIN TRY
-- Error is on this line
INSERT INTO linkedsrv1.db1.[dbo].tbl1 ([IdWebsite], [IdProductType])
VALUES (1200, 0)
END TRY
BEGIN CATCH
SELECT 'Insert Error', ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE()
RAISERROR (15600,-1,-1, 'INSERT ERROR');
END CATCH
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT 'Transaction Error', ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE()
IF XACT_STATE() != 0
ROLLBACK TRANSACTION
END CATCH

The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION

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

Properly scoped transactions in Stored Procs

Suppose I have a stored procedure that manages its own transaction
CREATE PROCEDURE theProc
AS
BEGIN
BEGIN TRANSACTION
-- do some stuff
IF #ThereIsAProblem
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
END
If I call this proc from an existing transaction, the proc can ROLLBACK the external transaction.
BEGIN TRANSACTION
EXEC theProc
COMMIT TRANSACTION
How do I properly scope the transaction within the stored procedure, so that the stored procedure does not rollback external transactions?
The syntax to do this probably varies by database. But in Transact-SQL what you do is check ##TRANCOUNT to see if you are in a transaction. If you are then you want to create a savepoint, and at the end you can just pass through the end of the function (believing a commit or rollback will happen later) or else rollback to your savepoint.
See Microsoft's documentation on savepoints for more.
Support for savepoints is fairly widespread, but I think the mechanism (assuming there is one) for finding out that you're currently in a transaction will vary quite a bit.
use ##trancount to see if you're already in a transaction when entering

Resources