MSSQL: What happens when an error occurs during trigger execution? - sql-server

Regarding Update and Insert triggers for MS SQL Server, is there a way to make them atomic? In other words, if an error occurs during the trigger, is it possible to automatically roll back the original insert or update?

After triggers are automatically part of the insert/update/delete atomic DML statement on a table.
You simply issue ROLLBACK TRAN in the trigger to rollback all work in the trigger and the original I/U/D statement. The outermost tran is also rolled back if there is one.
Important
Rollback in a trigger for SQL 2000 and earlier aborts the batch. No code after the offending I/U/D will run. See Erland great article and another
For SQL 2005 with TRY/CATCH, execution will go to the CATCH block and your batch (aka stored proc etc) will exit normally.
See Rollbacks and Commits in Stored Procedures and Triggers. The trigger and TRY/CATCH interation is here

There is a good article in MSDN that talks about triggers and transactions, located here:
http://msdn.microsoft.com/en-us/magazine/cc164032.aspx
Basically, you want to use transactions to make the operation atomic.

Related

SQL Server Rolls back my transaction when using THROW

I have an INSERT trigger on one of my tables that issues a THROW when it finds a duplicate. Problem is my transactions seem to be implicitly rolled back at this point - this is a problem, I want to control when transactions are rolled back.
The issue can be re-created with this script:
CREATE TABLE xTable (
id int identity not null
)
go
create trigger xTrigger on xTable after insert as
print 'inserting...';
throw 1600000, 'blah', 1
go
begin tran
insert into xTable default values
rollback tran
go
drop table xTable
If you run the rollback tran - it will tell you there is no begin tran.
If i swap the THROW for a 'normal' exception (like SELECT 1/0) the transaction is not rolled back.
I have checked xact_abort flag - and it is off.
Using SQL Server 2012 and testing through SSMS
Any help appreciated, thanks.
EDIT
After reading the articles posted by #Dan Guzman, i came to the following conclusion/summary...
SQL Server automatically sets XACT_ABORT ON in triggers.
My example (above) does not illustrate my situation - In reality I'm creating an extended constraint using a trigger.
My use case was contrived, I was trying to test multiple situations in the SAME unit test (not a real world situation, and NOT good unit test practice).
My handling of the extended constraint check and throwing an error in the trigger is correct, however there is no real situation in which I would not want to rollback the transaction.
It can be useful to SET XACT_ABORT OFF inside a trigger for a particular case; but your transaction will still be undermined by general batch-aborting errors (like deadlocks).
Historical reasons aside, i don't agree with SQL Server's handling of this; just because there is no current situation in which you'd like to continue the transaction, does not mean such a situation may not arise.
I'd like to see one able to setup SQL Server to maintain the integrity of transactions, if your chosen architecture is to have transactions strictly managed at origin, i.e. "he alone who starts the transaction, must finish it". This, aside from usual fail-safes, e.g. if your code is never reached due to system failure etc.
THROW will terminate the batch when outside the scope of TRY/CATCH (https://msdn.microsoft.com/en-us/library/ee677615.aspx). The implication here is that no further processing of the batch takes place, including the statements following the insert. You'll need to either surround your INSERT with a TRY/CATCH or use RAISERROR instead of THROW.
T-SQL error handing is a rather large and complex topic. I suggest you peruse the series of error-handling articles by Erland Sommarskog: http://www.sommarskog.se/error_handling/Part1.html. Most relevant here is the topic Can I Prevent the Trigger from Rolling Back the Transaction? http://www.sommarskog.se/error_handling/Part3.html#Triggers. The take away from a best practices point of view is that triggers are not the right solution if you enforce business rules in a trigger without a rollback.

Scripting using a transaction

I have a question but I can never get a clear answer. Any stored
procedure that used a transaction that I have looked at up until my recent job always had a commit transaction + a roll back in case of error. However I have seen a lot of code
at my new job that just has a begin transaction and then a commit at the end with no roll back. I understand why you would use a transaction with a rollback but why would you want to begin a transaction with no roll back? Is it so when you run that code you want to lock the table up so no values can be changed why your code is updating? If so why would you not want the added security of a roll back in case something goes wrong? Is this proper use of the transaction statement? Any thoughts or ideas would be great!
For Example:
BEGIN TRANSACTION [Tran1]
INSERT INTO [Test].[dbo].[T1]
([Title], [AVG])
VALUES ('Tidd130', 130), ('Tidd230', 230)
UPDATE [Test].[dbo].[T1]
SET [Title] = N'az2' ,[AVG] = 1
WHERE [dbo].[T1].[Title] = N'az'
COMMIT TRANSACTION [Tran1]
GO
shouldn't this code be using a roll back syntax for proper use of the begin transaction statement?
The idea is that if that set of transactions needs to be "all or nothing", wrapping the lot in a transaction is the way to ensure that is what will happen. You're not seeing an explicit rollback because that's not what they're guarding against. Imagine the ff scenario with your contrived example:
The insert happens
The server crashes (or the log fills up or some other external reason why things can't continue) before the update can happen
If they're both wrapped in the same transaction, the insert won't be reflected in the table data. Which is the desired behavior.
When transactions are not explicitly declared, SQL Server will automatically BEGIN and COMMIT a TRANSACTION for each command. This frees up each command's lock as soon as the command executes.
When executing multiple commands inside a single transaction (as in the example you posted), locks from all commands are held until the transaction is committed.
Depending on the desired behavior, the script you posted may be correct. However, I would be cautious to ensure that the developer did not mistakenly believe that the transaction would be automatically rolled back on error. If that behavior is desired, you do indeed need to explicitly ROLLBACK or SET XACT_ABORT ON
You use transaction when you need the outcome to be atomic, you would see this alot in financial related procedures where you are gravely worried about data acid consistency . Otherwise it is not necessary and introduces a great deal of locking overhead. There is a good question here and here that goes into great depth.
Edit
The takeaway point is if the procedure is a all or none and must either succeed or fail the correct decision is to use a transaction. If the procedure is not a all or none transaction such as simple insert update etc using a transaction is a) unnecessary and b) can introduce an undue performance overhead due to additional locking.

How to roll back UPDATE statement?

Is this possible without restoring whole database?
I have made changes which I would like to undo, but without putting DB offline, and doing full restore.
No, SQL Server does not have Ctrl + Z.
You protect yourself from this scenario by wrapping all DML statements in a transaction. So you have query windows with this:
BEGIN TRANSACTION;
UDPATE ...
-- COMMIT TRANSACTION;
-- ROLLBACK TRANSACTION;
When you run the update, verify that you updated the right number of rows, the right rows, the right way, etc. And then highlight either the commit or the rollback, depending on whether you performed the update correctly.
On the flip side, be careful with this, as it can mess you up the other way - begin a transaction, forget to commit or rollback, then go out for lunch, leave for the day, go on vacation, etc.
Unfortunately that will only help you going forward. In your current scenario, your easiest path is going to be to restore a copy of the database, and harvest the data from that copy (you don't need to completely over-write the current database to restore the data affected by this update).
The short answer is: No.
However, you don't have to take the DB offline to do a partial restore on a table or tables.
You can restore a backup to a separate database and then use TSQL queries to restore the rows that were negatively impacted by your update. This can take place while the main database is online.
More info on restoring a database to a new location:
http://technet.microsoft.com/en-us/library/ms186390.aspx
For future reference, as per my comment,
It is a good practice to use a TRANSACTION.
-- Execute a transaction statement before doing an update.
BEGIN TRANSACTION
... < your update code >
Then if the update is wrong or produces undesired results, you can ROLLBACK the TRANSACTION
-- Ooops I screwed up! Let's rollback!
--ROLLBACK TRANSACTION -- I have this commented out and then just select the command when needed. This helps to not accidentally rollback if you just press CTRL+E, (or F5 in SSMS 2012)
... and it goes away :)
When all is well you just COMMIT the TRANSACTION.
-- COMMIT TRANSACTION -- commented out, see above
Or else you lock the database for all users!
So don't forget to commit!
Yes, besides doing a full restore, there is a viable solution provided by 3rd party tool, which reads information from a database transaction log, parse it, and then creates an undo T-SQL script in order to rollback user actions
Check out the How to recover SQL Server data from accidental updates without backups online article for more information. The article is focused on the UPDATE operation, but with appropriate settings and filters, you can rollback any other database change that's recorded within the transaction log
Disclaimer: I work as a Product Support Engineer at ApexSQL
It is not possible unless you version your data appropriately or do a restore.
Possible but It will require lot of efforts.
SQL Server maintains logs for DELETED/UPDATED/INSERTED data in non-readable format and to read them you should have the efficient tool Event Log Analyzer.
As a slightly modified version to the answers above, I sometimes like to use an automatically rolled back transaction in combination with the OUTPUT keyword and the INSERTED internal table to see what will actually update as a result set.
For instance,
BEGIN TRANSACTION;
UPDATE TableA
SET TableA.Column1 = #SomeValue
OUTPUT INSERTED.*
WHERE <condition>
ROLLBACK TRANSACTION;
If the result set looks good to me, then I'll change the last statement to COMMIT TRANSACTION;.

How do I create a stored procedure whose effects cannot be rolled back?

I want to have a stored procedure that inserts a record into tableA and updates record(s) in tableB.
The stored procedure will be called from within a trigger.
I want the inserted records in tableA to exist even if the outermost transaction of the trigger is rolled back.
The records in tableA are linearly linked and I must be able to rebuild the linear connection.
Write access to tableA is only ever through the triggers.
How do I go about this?
What you're looking for are autonomous transactions, and these do not exist in SQL Server today. Please vote / comment on the following items:
http://connect.microsoft.com/SQLServer/feedback/details/296870/add-support-for-autonomous-transactions
http://connect.microsoft.com/SQLServer/feedback/details/324569/add-support-for-true-nested-transactions
What you can consider doing is using xp_cmdshell or CLR to go outside the SQL engine to come back in (these actions can't be rolled back by SQL Server)... but these methods aren't without their own issues.
Another idea is to use INSTEAD OF triggers - you can log/update other tables and then just decide not to proceed with the actual action.
EDIT
And along the lines of #VoodooChild's suggestion, you can use a #table variable to temporarily hold data that you can reference after the rollback - this data will survive a rollback, unlike an insert into a #temp table.
See this post Logging messages during a transaction for a (somewhat convoluted) effective way of achieving what you want: the insert into the logging table is persisted even if the transaction had rolled back. The method Simon proposes has several advantages: requires no changes to the caller, is fast and is scalable, and it can be used safely from within a trigger. Simon's example is for logging, but the insert can be for anything.
One way is to create a linked server that points to the local server. Stored procedures executed over a linked server won't be rolled back:
EXEC LinkedServer.DbName.dbo.sp_LogInfo 'this won''t be rolled back'
You can call a remote stored procedure from a trigger.

Any point in using explicit transaction for single update?

I have a stored procedure (SQL Server) which does nothing more than updates a single row in a table.
Does adding BEGIN TRANSACTION and COMMIT TRANSACTION before/after the update have any effect at all?
No. Transactions ensure that a set of modifying statements are atomic, namely that either all steps succeed or all steps fail. In case there is only one statement, there is no need to use transactions anymore ..
Anyway, T-SQL does that automaticaly. That's why they called it Transact SQL ...

Resources