假设我有两个表Product
和ProductSales
。
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解决方案
答案 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不能:
第二个变体 - 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