Transaction Savepoints

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.

Savepoints are useful in situations where errors are unlikely to occur. The use of a savepoint to roll back part of a transaction in the case of an infrequent error can be more efficient than having each transaction test to see if an update is valid before making the update. Updates and rollbacks are expensive operations, so savepoints are effective only if the probability of encountering the error is low and the cost of checking the validity of an update beforehand is relatively high.

This example shows the use of a savepoint in an order system in which there is a low probability of running out of stock because the company has effective suppliers and reorder points. Usually an application would verify that there is enough stock on hand before attempting to make the updates that would record the order. This example assumes that, for some reason, verifying the quantity of stock available beforehand is relatively expensive (due to connecting over a slow modem or WAN). The application could be coded just to make the update, and if it gets an error indicating that there is not enough stock, it would roll back the update. In this case, a quick check of @@ERROR after the insert is much faster than verifying the amount before the update.

The InvCtrl table has a CHECK constraint that triggers a 547 error if the QtyInStk column goes below 0. The OrderStock procedure creates a savepoint. If a 547 error occurs, it rolls back to the savepoint and returns the number of items on hand to the calling process. The calling process can then replace the order for the quantity on hand. If OrderStock returns a 0, this verifies to the calling process that there was enough stock on hand to satisfy the order.

SET NOCOUNT OFF;
GO
USE AdventureWorks2008R2;
GO
CREATE TABLE InvCtrl
    (WhrhousID      int,
    PartNmbr      int,
    QtyInStk      int,
    ReordrPt      int,
    CONSTRAINT InvPK PRIMARY KEY
    (WhrhousID, PartNmbr),
    CONSTRAINT QtyStkCheck CHECK (QtyInStk > 0) );
GO
CREATE PROCEDURE OrderStock
    @WhrhousID int,
    @PartNmbr int,
    @OrderQty int
AS
    DECLARE @ErrorVar int;
    SAVE TRANSACTION StkOrdTrn;
    UPDATE InvCtrl SET QtyInStk = QtyInStk - @OrderQty
        WHERE WhrhousID = @WhrhousID
        AND PartNmbr = @PartNmbr;
    SELECT @ErrorVar = @@error;
    IF (@ErrorVar = 547)
    BEGIN
        ROLLBACK TRANSACTION StkOrdTrn;
        RETURN (SELECT QtyInStk
                FROM InvCtrl
                WHERE WhrhousID = @WhrhousID
                AND PartNmbr = @PartNmbr);
    END
    ELSE
        RETURN 0;
GO