Savepoints are a better mechanism than 'nested transactions' for partial rollbacks. While attempting to study this rather uncommon feature - I found 3 key definitions from Books Online.
1 “Savepoints offer a mechanism to roll back portions of transactions. You create a savepoint using the SAVE TRANSACTION savepoint_name statement. Later, you execute a ROLLBACK TRANSACTION savepoint_name statement to roll back to the savepoint instead of rolling back to the start of the transaction.”
2 “The savepoint defines a location to which a transaction can return if part of the transaction is conditionally canceled. If a transaction is rolled back to a savepoint, it must proceed to completion with more Transact-SQL statements if needed and a COMMIT TRANSACTION statement, or it must be canceled altogether by rolling the transaction back to its beginning. To cancel an entire transaction, use the form ROLLBACK TRANSACTION transaction_name. All the statements or procedures of the transaction are undone.”
3 “ROLLBACK TRANSACTION without a savepoint_name or transaction_name rolls back to the beginning of the transaction. When nesting transactions, this same statement rolls back all inner transactions to the outermost BEGIN TRANSACTION statement. In both cases, ROLLBACK TRANSACTION decrements the @@TRANCOUNT system function to 0. ROLLBACK TRANSACTION savepoint_name does not decrement @@TRANCOUNT.”
Let us investigate this further to see what works and what does’nt. We also remember the following basics:
1 ‘Commit’ always commits the specific transaction while Rollback rolls back all transactions to the first.
2 Commit Transaction decrements @@trancount by 1, while Rollback Transaction sets it to 0.
Studying #1 , how does a ‘conditional cancel’ work?
Example 1:
Use Adventureworks
go
BEGIN TRANSACTION
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --Returns GustavG
SAVE TRANSACTION tran1 --Set a marker here onwards
UPDATE Person.Contact SET FirstName = 'GustavH' WHERE ContactID = 1
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --Returns GustavH
ROLLBACK TRANSACTION tran1
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --Goes back to GustavG
COMMIT TRANSACTION--Completes the transaction
This allows you to rollback the second transaction by itself. If this is used without a savepoint, the rollback would go all the way back to the first transaction.
Studying #2 – why should a savepoint ‘proceed to completion with commit or rollback – in other words why is it not possible to use ‘just savepoints’ – because savepoints do not alter @@trancount or close the transaction by themselves. In order to close the transaction and release locks you need a specific COMMIT or ROLLBACK transaction statement. Using the same example let us look at @@trancount at various stages…
BEGIN TRANSACTION
SELECT @@TRANCOUNT --shows 1 since we have one open transaction
UPDATE Person.Contact SET FirstName = 'GustavG' WHERE ContactID = 1
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --Returns GustavG
SAVE TRANSACTION tran1 --Set a marker here onwards
SELECT @@TRANCOUNT --still shows 1 showing that @@trancount is not altered by savepoint
UPDATE Person.Contact SET FirstName = 'GustavH' WHERE ContactID = 1
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --Returns GustavH
ROLLBACK TRANSACTION tran1
SELECT @@TRANCOUNT --still shows 1
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --Goes back to GustavG
COMMIT TRANSACTION--Completes the transaction and sets trancount to 0
SELECT @@TRANCOUNT --now shows 0
Studying #3 – It is known that Rollback transaction rolls back to the beginning of the transaction and only the outer most statement is true while you are using multiple transactions. But is it true that ROLLBACK TRANSACTION savepoint_name does not decrement @@TRANCOUNT?
BEGIN TRANSACTION
SELECT @@TRANCOUNT --returns 1
UPDATE Person.Contact SET FirstName = 'GustavG' WHERE ContactID = 1
SAVE TRANSACTION tran1
SELECT @@TRANCOUNT -- 1 again
UPDATE Person.Contact SET FirstName = 'GustavH' WHERE ContactID = 1
SAVE TRANSACTION tran2
SELECT @@TRANCOUNT -- STILL 1
UPDATE Person.Contact SET FirstName = 'GustavI' WHERE ContactID = 1
ROLLBACK TRANSACTION tran2
SELECT @@TRANCOUNT --STILL 1
COMMIT TRANSACTION tran1
SELECT @@TRANCOUNT --now this has gone back to 0!!
SELECT FirstName FROM Person.Contact WHERE ContactID = 1 --changes done by tran1 are committed.
SELECT @@TRANCOUNT --now this has gone back to 0!!
As seen the behavior of nested savepoints is not consistent with BOL statement that @@trancount is not affected by savepoints.
But savepoints can come in handy for partial rollbacks in comparision to nested transactions – if they are used with behavior in mind and appropriate BEGIN/END transaction statements.