I have long script which contains the Create tables, create schemas,insert data,update tables etc.I have to do this by only on script in batch wise.I ran it before but it created every time some error due to this some object will present inside the database. So In need some mechanism which can handle the batch execution if something goes wrong the whole script should be rolled back.
Appreciated Help and Time.
--343
Try this:
DECLARE #outer_tran int;
SELECT #outer_tran = ##TRANCOUNT;
-- find out whether we are inside the outer transaction
-- if yes - creating save point if no starting own transaction
IF #outer_tran > 0 SAVE TRAN save_point ELSE BEGIN TRAN;
BEGIN TRY
-- YOUR CODE HERE
-- if no errors and we have started own transaction - commit it
IF #outer_tran = 0 COMMIT;
END TRY
BEGIN CATCH
-- if error occurred - rollback whole transaction if it is own
-- or rollback to save point if we are inside the external transaction
IF #outer_tran > 0 ROLLBACK TRAN save_point ELSE ROLLBACK;
--and rethrow original exception to see what happens
DECLARE
#ErrorMessage nvarchar(max),
#ErrorSeverity int,
#ErrorState int;
SELECT
#ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH
While I might not have caught all the nuances of your question, I believe XACT_ABORT will deliver the functionality you seek. Simply add a
SET XACT_ABORT ON;
to the beginning of your script.
With the 2005 release of SQL Server, you have access to try/catch blocks in TSQL as well.
Related
My colleague has this in a procedure:
BEGIN TRAN
--Some deletes and inserts
IF(##error <> 0)
BEGIN
ROLLBACK TRAN
RETURN
END
COMMIT TRAN
I have another in a stored procedure that simply is:
BEGIN TRANSACTION
--Some deltes and inserts
COMMIT TRANSACTION
I have tested and found that my procedure always rolls everything back during an error (tested for example changing a column data type etc.) without explicitly coding a rollback. Also I have read that using ##error condition is outdated for SQL Server 2005 and above.
What would you say is the correct way of doing a transaction for SQL Server 2008 R2 and above? Thanks
YES, the ROLLBACK is necessary!
I would do a stored procedure based on this template for SQL Server 2005 and newer:
BEGIN TRANSACTION
BEGIN TRY
-- put your T-SQL commands here
-- if successful - COMMIT the work
COMMIT TRANSACTION
END TRY
BEGIN CATCH
-- handle the error case (here by displaying the error)
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
-- in case of an error, ROLLBACK the transaction
ROLLBACK TRANSACTION
-- if you want to log this error info into an error table - do it here
-- *AFTER* the ROLLBACK
END CATCH
There a problem with the ##ERROR variable.
It's a global variable thus if you are doing something like:
BEGIN TRAN
--inserts
--deletes
--updates
-- last operation
IF(##error <> 0)
BEGIN
ROLLBACK TRAN
RETURN
END
COMMIT TRAN
##error contains the result for the last operation only. Thus this piece of code can mask error in previous operations.
My advice is, if you can manage transaction at application level, do it at application level.
Handling errors at server side is not for faint hearts and it doesn't improves your application overral robusteness.
Create the following procedure in your DB then in your catch block, simply exec RethrowError.
The nice thing about this is that you dont have to pass any parameters into it from your main stored procedure
CREATE PROCEDURE [dbo].[RethrowError] AS
-- Return if there is no error information to retrieve.
IF ERROR_NUMBER() IS NULL
RETURN;
DECLARE
#ErrorMessage NVARCHAR(4000),
#ErrorNumber INT,
#ErrorSeverity INT,
#ErrorState INT,
#ErrorLine INT,
#ErrorProcedure NVARCHAR(200);
-- Assign variables to error-handling functions that
-- capture information for RAISERROR.
SELECT
#ErrorNumber = ERROR_NUMBER(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE(),
#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
-- Building the message string that will contain original
-- error information.
SELECT #ErrorMessage =
N'Error %d, Level %d, State %d, %s, Line %d' + ERROR_MESSAGE();
-- Raise an error: msg_str parameter of RAISERROR will contain
-- the original error information.
RAISERROR
(
#ErrorMessage,
#ErrorSeverity,
1,
#ErrorNumber, -- parameter: original error number.
#ErrorSeverity, -- parameter: original error severity.
#ErrorState, -- parameter: original error state.
#ErrorProcedure, -- parameter: original error procedure name.
#ErrorLine -- parameter: original error line number.
);
GO
CREATE PROCEDURE YourProcedure
AS
BEGIN TRANSACTION
BEGIN TRY
--Put your code in here
END TRY
BEGIN CATCH
EXEC RethrowError
END CATCH
END
I have the process scenario on SQL Server 2008R2:
• A usp to gather data and then a transfer data between two SQL Servers
This process is to be done with transaction at all levels of the process (usp, SSIS, and trigger)
In the data flow transferring the data to DB7.dbo.Dest, this table has an AFTER INSERT trigger which inserts the data that just came through into the final table DB7.dbo.FinalDestination:
CREATE TRIGGER [dbo].[Insert_OnStaging] ON [dbo].[Dest]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; --Rollsback complete transaction if there are any errors
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO [DB7].[dbo].[FinalDestination] WITH (TABLOCK)
(Column1
,Column2
)
SELECT I.Column1, I.Column2
FROM INSERTED I
INNER JOIN [DB7].[dbo].[Dest] PR
ON I.IDcol = PR.IDcol
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 AND XACT_STATE() <> 0
ROLLBACK TRANSACTION;
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
DECLARE #ErrorLine INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE()
;
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState, -- State.
#ErrorLine --Error Line
);
END CATCH;
END
At every level, I have tried to be defensive about the data due to the sensitivity of the data properly and completely reaching the final table.
In regards to the SSIS, from what I've read and tested it seems to work fine.
My biggest concern is the trigger which I scripted above. From my reading and understanding, setting XACT_ABORT ON will roll back the transaction inside the TRY block if any errors (in other words, there is an uncommitable transaction). In this case I went ahead and still added the rollback transaction portion in the CATCH block as a piece of mind since it would never reach (from my understanding). At the same time, I added the WITH (TABLOCK) option in order to lock the table while performing the INSERT.
In the case of the trigger, is the TRY...CATCH even necessary with the XACT_ABORT being ON? Is the COMMIT TRANSACTION necessary inside the TRY block? As I have also seen it committed after the CATCH block based on the ##TRANCOUNT
BEGIN TRY
BEGIN TRANSACTION
[Tsql here]
END TRY
BEGIN CATCH
[Error Handling]
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION
END
Answers and critique are welcomed and thank you in advance. Please excuse any typos as I tried to generalize the names...
You need TRY..CATCH even though you're using XACT_ABORT. XACT_ABORT aborts the tran but continues running the batch/procedure! This is very, very nasty behavior. It means that DML/DDL can still run after an error has occurred but outside of a transaction so that you can never roll it back.
SQL Server does not have any mechanism to avoid that except for TRY..CATCH. I'm not sure what XACT_ABORT is ever good for. In your example neither does it help not does it hurt.
And yes, you can move the COMIT outside the TRY if you want to. Just make sure to properly balance it with the BEGIN TRAN.
I have a stored procedure in SQL Server with try catch. What I want to do in the catch loop is to call my own Stored procedure for logging with all the error variables, like so:
BEGIN TRY
-- Generate a divide-by-zero error.
SELECT 1/0;
END TRY
BEGIN CATCH
exec log.LogError ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_MESSAGE();
END CATCH;
When I run this I get an error on the parenthesis.
I can run:
select ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_MESSAGE();
I can also do
print ERROR_NUMBER()
What I want to do is the have just one line which calles the stored procedure with the parameters because I will have this in many stored procedures and don't want to have lot's of code setting the error parameters (I will have more than these three) in each stored procedure where I have try-catch.
Does anybody know how I can pass these into another stored procedure?
Regards,
Johann
Unfortunately T-SQL is not a DRY code reuse compact syntax programmer friendly language. You have to do it the hard way, and that implies writing a minimum of 4-5 lines of code inside each CATCH block. Besides, you need to account also for transaction semantics: has it rolled back or not? Or worse, are you in a doomed transaction? That's why I created this T-SQL error handling template:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
return;
end catch
end
Is it longer than what you're looking for? I bet. It is correct? Yes.
And finally, how do you handle logging in a transactional environment? Inserts into a log table will be rolled back along with everything else in case of error. Sometimes that is OK, other times is even desired, but sometimes is problematic. One of the most interesting solutions is Simon Sabin's Logging messages during a transaction.
Try changing your log.LogError procedure so it accesses ERROR_NUMBER() and the other error functions directly. There's an example in the documentation.
At the moment I write stored procedures this way:
create proc doStuff
#amount int
as
begin try
begin tran
...
if something begin select 'not_good' rollback return end
if someelse begin select 'some_other_thing' rollback return end
--do the stuff
...
commit
end try
begin catch
if ##trancount > 0 rollback
select 'error'
end catch
the problem with this approach is that I hide the error, anybody knows to do this some other ?
What database are you using? In SQL Server you can use the keyword RASIERROR to generate error messages. See RAISERROR (Transact-SQL)
Assuming SQL Server here, since that looks a lot like SQL Server syntax:
Preferably, you should also use SAVE TRAN so you can treat the procedure as its own unit of work and let the caller choose whether or not to rollback the entire transaction (as opposed to only rolling back the work in this particular block). Remus Rusanu wrote an article about that a while back.
Putting that aside for the moment, you need to save the error immediately after you catch it and then re-raise it after rolling back (normally with some additional info):
CREATE PROCEDURE xyz [params]
AS
BEGIN
BEGIN TRY
BEGIN TRAN
-- Do the work
COMMIT
END TRY
BEGIN CATCH
DECLARE
#Error int,
#Severity int,
#Message varchar(4000)
SELECT
#Error = ERROR_NUMBER(),
#Severity = ERROR_SEVERITY(),
#Message = ERROR_MESSAGE()
ROLLBACK
RAISERROR('Procedure xyz: %d: %s', #Severity, 1, #Error, #Message)
END CATCH
END
SQL server sp.
create procedure doStuff
(
#amount int
)
as
begin try
begin transaction
if something
raiserror('not_good', 16, 1)
if somethingelse
raiserror('some_other_thing', 16, 1)
do stuff here
commit
end try
begin catch
if ##trancount > 0
rollback
declare #errmsg nvarchar(4000), #errseverity int
select #errmsg = error_message(), #errseverity = error_severity()
raiserror(#errmsg, #errseverity, 1)
end catch
I'm interested in the side effects and potential problems of the following pattern:
CREATE PROCEDURE [Name]
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
[...Perform work, call nested procedures...]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
END
To the best of my understanding this pattern is sound when used with a single procedure - the procedure will either complete all of its statements without error, or it will rollback all actions and report the error.
However when one stored procedure calls another stored procedure to do some sub-unit of work (with the understanding that the smaller procedure is sometimes called on its own) I see an issue coming about with relation to rollbacks - an informational message (Level 16) is issued stating The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.. This I assume is because the rollback in the sub-procedure is always rolling back the outer-most transaction, not just the transaction started in the sub-procedure.
I do want the whole thing rolled back and aborted if any error occurs (and the error reported to the client as an SQL error), I'm just not sure of all the side effects that come from the outer layers trying to rollback a transaction that has already been rolled back. Perhaps a check of ##TRANCOUNT before doing a rollback at each TRY CATCH layer?
Finally there is the client end (Linq2SQL), which has it's own transaction layer:
try
{
var context = new MyDataContext();
using (var transaction = new TransactionScope())
{
// Some Linq stuff
context.SubmitChanges();
context.MyStoredProcedure();
transactionComplete();
}
}
catch
{
// An error occured!
}
In the event that a stored procedure, "MySubProcedure", called inside MyStoredProcedure raises an error, can I be sure that everything previously done in MyStoredProcedure will be rolled back, all the Linq operations made by SubmitChanges will be rolled back, and finally that the error will be logged? Or what do I need to change in my pattern to ensure the whole operation is atomic, while still allowing the child parts to be used individually (i.e. the sub-procedures should still have the same atomic protection)
This is our template (error logging removed)
This is designed to handle
Paul Randal's article "No such thing as a nested transaction in SQL Server"
Error 266
Trigger Rollbacks
Explanations:
all TXN begin and commit/rollbacks must be paired so that ##TRANCOUNT is the same on entry and exit
mismatches of ##TRANCOUNT cause error 266 because
BEGIN TRAN increments ##TRANCOUNT
COMMIT decrements ##TRANCOUNT
ROLLBACK returns ##TRANCOUNT to zero
You can not decrement ##TRANCOUNT for the current scope
This is what you'd think is the "inner transaction"
SET XACT_ABORT ON suppresses error 266 caused by mismatched ##TRANCOUNT
And also deals with issues like this "SQL Server Transaction Timeout" on dba.se
This allows for client side TXNs (like LINQ)
A single stored procedure may be part of distributed or XA transaction, or simply one initiated in client code (say .net TransactionScope)
Usage:
Each stored proc must conform to the same template
Summary
So don't create more TXNs than you need
The code
CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0
BEGIN TRANSACTION
[...Perform work, call nested procedures...]
IF #starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION;
THROW;
--before SQL Server 2012 use
--RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
GO
Notes:
The rollback check is actually redundant because of SET XACT_ABORT ON. However, it makes me feel better, looks odd without, and allows for situations where you don't want it on
Remus Rusanu has a similar shell that uses save points. I prefer an atomic DB call and don't use partial updates like their article
I am not a Linq guy (and neither is Erland), but he wrote the absolute bibles on error handling. Outside of the complications Linq might add to your problem, all of your other questions should be answered here:
http://www.sommarskog.se/error_handling/Part1.html
(Old link: http://www.sommarskog.se/error_handling_2005.html)
To solve the issue of returning the error number and line number mentioned by #AlexKuznetsov, one can raise the error as such:
DECLARE #ErrorMessage NVARCHAR(4000)
DECLARE #ErrorSeverity INT
DECLARE #ErrorState INT
DECLARE #ErrorLine INT
DECLARE #ErrorNumber INT
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorNumber = ERROR_NUMBER(),
#ErrorLine = ERROR_LINE()
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState, #ErrorNumber, #ErrorLine)
-- #Amanda method above doesnt return correct error number
DECLARE
#ErrorMessage nvarchar(4000),
#ErrorSeverity int,
#ErrorState int,
#ErrorLine int,
#ErrorNumber int
BEGIN TRY
SELECT 1/0; -- CATCH me
END TRY
BEGIN CATCH
DECLARE #err int = ##ERROR
PRINT #err -- 8134, divide by zero
PRINT ERROR_NUMBER() -- 8134
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorNumber = ERROR_NUMBER(),
#ErrorLine = ERROR_LINE()
-- error number = 50000 :(
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState, #ErrorNumber, #ErrorLine)
END CATCH
-- error number = 8134
SELECT 1/0
In case no special error handling needed in CATCH except rethrow and stored procs call chain isn't too long it may be suitable to use such simple template:
create procedure someNestedSP
as
SET XACT_ABORT ON
begin transaction
-- do some work or call some other similar SP
commit transaction
It would also rollback root transaction with all "nested" ones in case of any error but the code is shorter and more straightforward than #gbn's solution. Still XACT_ABORT takes care of most issues mentioned there.
There may be addiotional overhead for transaction nesting but it may be not too high, I guess.