After two days tests on batch, roll back and try ..catch, my mind is still vague. I separate what I was doing into two step in order to clear my question.
1. roll back a batch
As online book explains, in a batch, executed statements cannot be roll back only except the batch is in a transaction and error in the batch cause the transaction is roll back.
So I put the batch into a transaction like
begin transaction
create table A ...
insert into A values...
insert into A values... (error here!)
insert into A values...
GO
rollback
This works with error output and no table was created
(1 row(s) affected)
Msg 213, Level 16, State 1, Line 5
Column name or number of supplied values does not match table definition.
Msg 208, Level 16, State 1, Line 1
Invalid object name 'A'.
However, the rollback will be executed anyway even no error in transaction. In order to deal with this case, I use TRY ...CATCH as in 2.
2. use TRY ...CATCH
BEGIN TRY
begin transaction
create table A ...
insert into A values...
insert into A values... (error here!)
insert into A values...
--GO
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
This time it doesn't allow statement GO here any more. S*o Batch is whole block between BEGIN TRY AND END TRY in this case?*
In addition, the result is not as I expected. The CREATE TABLE AND first insert were still executed and didn't roll back.
I searched again. It seems I need to SET XACT_ABORT ON in order to record these executed statement as uncommitted before touch commit. What I am understanding here is right? If so, I didn't add any commit statement in this case.
By the way, test are done on SQL SERVER 2012. Thanks for any clarification!
The reason it doesn't allow the GO statement is that the try and catch must be part of the same batch as metioned in this MSDN article. It states;
"Each TRY…CATCH construct must be inside a single batch, stored
procedure, or trigger. For example, you cannot place a TRY block in
one batch and the associated CATCH block in another batch. The
following script would generate an error:"
BEGIN TRY
SELECT *
FROM sys.messages
WHERE message_id = 21;
END TRY
GO
-- The previous GO breaks the script into two batches,
-- generating syntax errors. The script runs if this GO
-- is removed.
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNumber;
END CATCH;
GO
For an alternative idea on how to handle this, take a look at gbns answer on the question Nested stored procedures containing TRY CATCH ROLLBACK pattern? as he discusses his pattern/template for handling transactions including the use of (and reason for) XACT_ABORT and other nifty features. I'd also suggest reading the associated links in gbns answer
Aaron Betrand's answer to the same question refers to a Erland Somarsskog's article on error handling that is very similar to gbn's answer also.
Even though the title of the original question relates to nested transactions, it is still applicable in your situation I believe.
Try using SET XACT_ABORT ON in the begining of your script.
Check out XACT_ABORT MDSN reference page here
Related
Can We use GO statement mutiple times in a SQL Transaction. I am having a long T-SQL script and I want to run it in a SQL Transaction. If all goes well then I will commit otherwise will rollback.
But, While running that query I got error like 'create function must be the only statement in the batch'. As I am creating and dropping many Functions and Procedures in that.
I have not used GO anywhere in the script. My question is that - Can I use multiple times GO statement in that long script. Because, GO creates a batch and if batch executes successfully first time but fails next time then will rollback transaction statement be able to actually rollback that has been executed ?
Structure of my script looks like :
PRINT 'Transaction Started'
BEGIN TRY
BEGIN TRAN
Drop Function
....
....
Create Function
....
....
Drop Procedure
....
....
Lots of statements
....
....
COMMIT TRAN
PRINT 'Transaction Succeeded'
END TRY
BEGIN CATCH
PRINT 'Transaction Failed'
IF(##TRANCOUNT > 0)
ROLLBACK TRAN
END CATCH
I am creating this script to migrate some changes from newDB to oldDB in a single script.
You are mixing concepts. GO is not a Transact-SQL concept, not part of the language, and not understood by SQL Server. GO is the tools batch delimiter. sqlcmd.exe and SSMS both are using, by default, GO as the batch delimiter. The batch delimiter is used to identify the individual batches inside the SQL source file. The client tool sends to the server one batch at a time (of course, omitting the delimiter).
Transactions can span batches. TRY/CATCH blocks cannot. CREATE/ALTER statements must be the only statement in a batch (comments are not statements, and statements contained in a function procedure body are,well, contained).
Something similar to what you want to do can be achieved by starting a transaction and abortign the execution on first error (-b at sqlcmd.exe start, or use :on error exit in SSMS).
But doing DDL inside long transactions is not going to work. Specially if you plan to mix it with DML. Most corruptions I had to investigate come from this combination (Xact, DDL + DML, rollback). I strongly recommend against it.
The sole way to deploy schema updates safely is to take a backup, deploy, restore from backup if something goes wrong.
Note that what Dan recommends (dynamic SQL) works because sp_executesql starts a new, inner, batch. This batch will satisfy the CREATE/ALTER restrictions.
Note that GO is not a SQL keyword. It is a client-side batch separator used by SQL Server Management Studio and other client tools.
GO has no effect on transaction scope. BEGIN TRAN will start a transaction on the current connection. COMMIT and ROLLBACK will end the transaction. You can execute as many statements as you want in-between. GO will execute the statements separately.
As specified by MSDN:
A TRY…CATCH construct cannot span multiple batches.
So BEGIN TRY, END TRY, BEGIN CATCH, and END CATCH cannot be separated into separate batches by a GO separator. They must appear in the same query.
If you do try to include a batch separator in a TRY/CATCH statement like the invalid SQL below:
begin try
go
end try
begin catch
go
end catch
This will execute 3 different queries that return syntax errors:
1) begin try
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near 'begin'.
2) end try begin catch
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near 'try'.
3) end catch
Msg 102, Level 15, State 1, Line 6
Incorrect syntax near 'catch'.
GO is a nice keyword to use. The GO will complete the last code block and continue on to the next block. Yes you can use multiple GOs in a statement to break it up into multiple batches. But it would be better to use try/catch logic with a combination of GOs since you are doing transaction based logic. https://msdn.microsoft.com/en-us/library/ms175976.aspx this site gives you some examples on how to use it and if you run into a hitch you can output that error and continue on if you choose.
I have created AFTER INSERT TRIGGER
Now if any case if an error occurs while executing Trigger. It should not effect Insert Operation on Triggered table.
In One word if any ERROR occurs in trigger it should Ignore it.
As I have used
BEGIN TRY
END TRY
BEGIN CATCH
END CATCH
But it give following error message and Rolled back Insert operation on Triggered table
An error was raised during trigger execution. The batch has been
aborted and the user transaction, if any, has been rolled back.
Interesting problem. By default, triggers are designed that if they fail, they rollback the command that fired it. So whenever trigger is executing there is an active transaction, whatever there was an explicit BEGIN TRANSACTION or not on the outside. And also BEGIN/TRY inside trigger will not work. Your best practice would be not to write any code in trigger that could possibly fail - unless it is desired to also fail the firing statement.
In this situation, to suppress this behavior, there are some workarounds.
Option A (the ugly way):
Since transaction is active at the beginning of trigger, you can just COMMIT it and continue with your trigger commands:
CREATE TRIGGER tgTest1 ON Test1 AFTER INSERT
AS
BEGIN
COMMIT;
... do whatever trigger does
END;
Note that if there is an error in trigger code this will still produce the error message, but data in Test1 table are safely inserted.
Option B (also ugly):
You can move your code from trigger to stored procedure. Then call that stored procedure from Wrapper SP that implements BEGIN/TRY and at the end - call Wrapper SP from trigger. This might be a bit tricky to move data from INSERTED table around if needed in the logic (which is in SP now) - probably using some temp tables.
SQLFiddle DEMO
You cannot, and any attempt to solve it is snake oil. No amount of TRY/CATCH or ##ERROR check will work around the fundamental issue.
If you want to use the tightly coupling of a trigger then you must buy into the lower availability induced by the coupling.
If you want to preserve the availability (ie. have the INSERT succeed) then you must give up coupling (remove the trigger). You must do all the processing you were planning to do in the trigger in a separate transaction that starts after your INSERT committed. A SQL Agent job that polls the table for newly inserted rows, an Service Broker launched procedure or even an application layer step are all going to fit the bill.
The accepted answer's option A gave me the following error: "The transaction ended in the trigger. The batch has been aborted.". I circumvented the problem by using the SQL below.
CREATE TRIGGER tgTest1 ON Test1 AFTER INSERT
AS
BEGIN
SET XACT_ABORT OFF
BEGIN TRY
SELECT [Column1] INTO #TableInserted FROM [inserted]
EXECUTE sp_executesql N'INSERT INTO [Table]([Column1]) SELECT [Column1] FROM #TableInserted'
END TRY
BEGIN CATCH
END CATCH
SET XACT_ABORT ON
END
I need to provide an auto update feature to my application.
I am having problem in applying the SQL updates. I have the updated SQL statement in my .sql file and what i want to achieve is that if one statment fails then entire script file must be rolled back
Ex.
create procedure [dbo].[test1]
#P1 varchar(200),
#C1 int
as
begin
Select 1
end
GO
Insert into test (name) values ('vv')
Go
alter procedure [dbo].[test2]
#P1 varchar(200),
#C1 int
as
begin
Select 1
end
GO
Now in the above example, if i get the error in third statement of "alter procedure [dbo].[test2]" then i want to rollback the first two changes also which is creating SP of "test1" and inserting data into "test" table
How should i approach this task? Any help will be much appreciated.
If you need any more info then let me know
Normally, you would want to add a BEGIN TRAN at the beginning, remove the GO statements, and then handle the ROLLBACK TRAN/COMMIT TRAN with a TRY..CATCH block.
When dealing with DML though there are often statements that have to be at the start of a batch, so you can't wrap them in a TRY..CATCH block. In that case you need to put together a system that knows how to roll itself back.
A simple system would be just to backup the database at the start and restore it if anything fails (assuming that you are the only one accessing the database the whole time). Another method would be to log each batch that runs successfully and to have corresponding rollback scripts which you can run to put everything back should a later batch fail. This obviously requires much more work (writing an undo script for every script PLUS fully testing the rollbacks) and can also be a problem if people are still accessing the database while the upgrade is happening.
EDIT:
Here's an example of a simple TRY..CATCH block with transaction handling:
BEGIN TRY
BEGIN TRANSACTION
-- All of your code here, with `RAISERROR` used for any of your own error conditions
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
However, the TRY..CATCH block cannot span batches (maybe that's what I was thinking of when I said transactions couldn't), so in your case it would probably be something more like:
IF (OBJECT_ID('dbo.Error_Happened') IS NOT NULL)
DROP TABLE dbo.Error_Happened
GO
BEGIN TRANSACTION
<Some line of code>
IF (##ERROR <> 0)
CREATE TABLE dbo.Error_Happened (my_id INT)
IF (OBJECT_ID('dbo.Error_Happened') IS NOT NULL)
BEGIN
<Another line of code>
IF (##ERROR <> 0)
CREATE TABLE dbo.Error_Happened (my_id INT)
END
...
IF (OBJECT_ID('dbo.Error_Happened) IS NOT NULL)
BEGIN
ROLLBACK TRANSACTION
DROP TABLE dbo.Error_Happened
END
ELSE
COMMIT TRANSACTION
Unfortunately, because of the separate batches from the GO statements you can't use GOTO, you can't use the TRY..CATCH, and you can't persist a variable across the batches. This is why I used the very kludgy trick of creating a table to indicate an error.
A better way would be to simply have an error table and look for rows in it. Just keep in mind that your ROLLBACK will remove those rows at the end as well.
I am working on pymssql, a python MSSQL driver. I have encountered an interesting situation that I can't seem to find documentation for. It seems that when a CREATE TABLE statement fails, the transaction it was run in is implicitly rolled back:
-- shows 0
select ##TRANCOUNT
BEGIN TRAN
-- will cause an error
INSERT INTO foobar values ('baz')
-- shows 1 as expected
select ##TRANCOUNT
-- will cause an error
CREATE TABLE badschema.t1 (
test1 CHAR(5) NOT NULL
)
-- shows 0, this is not expected
select ##TRANCOUNT
I would like to understand why this is happening and know if there are docs that describe the situation. I am going to code around this behavior in the driver, but I want to make sure that I do so for any other error types that implicitly rollback a transaction.
NOTE
I am not concerned here with typical transactional behavior. I specifically want to know why an implicit rollback is given in the case of the failed CREATE statement but not with the INSERT statement.
Here is the definitive guide to error handling in Sql Server:
http://www.sommarskog.se/error-handling-I.html
It's long, but in a good way, and it was written for Sql Server 2000 but most of it is still accurate. The part you're looking for is here:
http://www.sommarskog.se/error-handling-I.html#whathappens
In your case, the article says that Sql Server is performing a Batch Abortion, and that it will take this measure in the following situations:
Most conversion errors, for instance conversion of non-numeric string to a numeric value.
Superfluous parameter to a parameterless stored procedure.
Exceeding the maximum nesting-level of stored procedures, triggers and functions.
Being selected as a deadlock victim.
Mismatch in number of columns in INSERT-EXEC.
Running out of space for data file or transaction log.
There's a bit more to it than this, so make sure to read the entire section.
It is often, but not always, the point of a transaction to rollback the entire thing if any part of it fails:
http://www.firstsql.com/tutor5.htm
One of the most common reasons to use transactions is when you need the action to be atomic:
An atomic operation in computer
science refers to a set of operations
that can be combined so that they
appear to the rest of the system to be
a single operation with only two
possible outcomes: success or failure.
en.wikipedia.org/wiki/Atomic_(computer_science)
It's probably not documented, because, if I understand your example correctly, it is assumed you intended that functionality by beginning a transaction with BEGIN TRAN
If you run as one batch (which I did first time), the transaction stays open because the INSERT aborts the batch and CREATE TABLE is not run. Only if you run line-by-line does the transaction get rolled back
You can also generate an implicit rollback for the INSERT by setting SET XACT_ABORT ON.
My guess (just had a light bulb moment as I typed the sentence above) is that CREATE TABLE uses SET XACT_ABORT ON internalls = implicit rollback in practice
Some more stuff from me on SO about SET XACT_ABORT (we use it in all our code because it releases locks and rolls back TXNs on client CommandTimeout)
I apologise in advance for this stupid question, but I cannot figure why CREATE TABLE is not rolled back in the code shown below. I know that CREATE DATABASE, CREATE FULLTEXT CATALOG, CREATE FULLTEXT INDEX cannot be specified witnin user-defined transaction. Please note that Tables folder within SSMS gets locked whilst executing this code.
BEGIN TRANSACTION T1
CREATE TABLE temp
(
chisla char(1)
)
SELECT count(chisla) AS Count, chisla AS My_Numbers
FROM temp
--GROUP BY chisla
ORDER BY chisla
drop table temp
COMMIT TRANSACTION T1
GO
You didn't tell it to rollback. JNK shows how the try Catch shoudl be done to rollback a transacation in case of a trappable error. However in this case with incorrect SQL, it wouldn't rollback in any event because it is a non-trappable error. You must have correct syntax in the SQl for transactions to work correctly which is one reason why dynamic SQl can be very dangerous as it is impossible to fully test.
It is a bad practice to be creating tables on the fly like this anyway. If you want something temporaily, use a temp table or table variable, do not create a permanent table that you expect to rollback the creating of if the action fails.
I think you want a TRY...CATCH Block. There's a very nice explanation here on msdn.
For a quick example, though:
BEGIN TRY
BEGIN TRANSACTION
...your code...
COMMIT TRANSACTION
END TRY
BEGIN CATCH
...error reporting code here...
ROLLBACK TRANSACTION
END CATCH;