存储过程内的SQL Server事务

时间:2013-11-29 15:28:45

标签: sql sql-server

假设我有两个表ProductProductSales

Product表有:

ProductID(int)(pk)
Name(varchar2)
UnitPrice(float) 
ProductAvailable(int)

ProductSales表有:

ProductSalesID(int)(pk)
ProductID(int)
Quantity(int)

我想在存储过程中创建一个事务,首先检查Quantity是否小于ProductAvailable。如果它大于Rollback,则事务还从Quantity中扣除ProductAvailable(由用户提供)并插入ProductSales表。 如何使用SQL Server解决方案

1 个答案:

答案 0 :(得分:0)

并发更新解决方案:

-- Test Data
CREATE TABLE Product (
    ProductID int NOT NULL IDENTITY PRIMARY KEY CLUSTERED
    ,Name varchar(256) NOT NULL
    ,UnitPrice money NOT NULL
    ,ProductAvailable int NOT NULL)

CREATE TABLE ProductSales (
    ProductSalesID int NOT NULL IDENTITY PRIMARY KEY CLUSTERED
    ,ProductID int NOT NULL
    ,Quantity int NOT NULL)

INSERT INTO Product (Name, UnitPrice, ProductAvailable)
VALUES ('Prod1', 5.0, 10)
    ,('Prod2',6.0, 5)
GO

第一个变体 - MSDN OUTPUT Clause

CREATE PROCEDURE Sale 
    @ProductID int
    ,@Quantity int
AS
BEGIN
    IF @Quantity <= 0 RETURN -1;

    -- atomic operation
    UPDATE Product
        SET ProductAvailable = ProductAvailable - @Quantity
    OUTPUT INSERTED.ProductID, @Quantity INTO ProductSales (ProductID, Quantity)
    WHERE ProductID = @ProductID
        AND ProductAvailable >= @Quantity;

    IF @@ROWCOUNT = 0 RETURN -1;
END
GO

但它有限制 - output_table不能

  • 已启用在其上定义的触发器。
  • 参与FOREIGN KEY约束的任何一方。
  • 拥有CHECK约束或启用规则。

第二个变体 - MSDN Transaction Statements

CREATE PROCEDURE Sale2
    @ProductID int
    ,@Quantity int
AS
BEGIN
    DECLARE @ProcID CHAR(36) = NEWID()
            ,@TranCount Int = @@TranCount
            ,@ProductAvailable int
            ,@errorMessage nvarchar(4000)
            ,@errorSeverity int
            ,@errorState int
            ,@ReturnCode int = -1;

    IF @Quantity <= 0 RETURN -1;

    IF (@TranCount = 0)
        BEGIN TRANSACTION;
    ELSE
        SAVE TRANSACTION @ProcID;

    BEGIN TRY
        SELECT @ProductAvailable = ProductAvailable 
        FROM Product WITH (XLOCK, HOLDLOCK)
        WHERE ProductID = @ProductID;

        -- Record is locked, you can do any checks here
        IF @ProductAvailable >= @Quantity
            BEGIN
                UPDATE Product
                    SET ProductAvailable = ProductAvailable - @Quantity
                WHERE ProductID = @ProductID;

                INSERT INTO ProductSales (ProductID, Quantity)
                    VALUES(@ProductID, @Quantity);

                SET @ReturnCode  = 1;
            END
        ELSE
            SET @ReturnCode  = -1;

        IF (@TranCount = 0)
            COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF (@TranCount = 0)
                ROLLBACK TRANSACTION;
        ELSE
            IF (XACT_STATE() <> -1)
                ROLLBACK TRANSACTION @ProcID;

        SELECT @errorMessage = 'Error in [Sale2]: ' + ERROR_MESSAGE(), @errorSeverity = ERROR_SEVERITY(), @errorState = ERROR_STATE();
        RAISERROR (@errorMessage, @errorSeverity, @errorState);
        SET @ReturnCode  = -1;
    END CATCH

    RETURN @ReturnCode;
END
GO

检查结果

-- Test
DECLARE @RetCode int

EXEC @RetCode = Sale @ProductID = 1, @Quantity = 7
SELECT @RetCode as RetCode

EXEC @RetCode = Sale2 @ProductID = 1, @Quantity = 2
SELECT @RetCode as RetCode

EXEC @RetCode = Sale @ProductID = 2, @Quantity = 7
SELECT @RetCode as RetCode
GO
SELECT * FROM ProductSales
SELECT * FROM Product
GO