我想用SQL做:
但这必须是并发证明,这意味着一旦完成第1步,每个其他实例都需要等待第1步,直到第3步结束,因为它更改了数据以进行计算。这是一个简化的例子(我省略了一些声明并使用了硬编码值):
CREATE PROCEDURE AddOrder AS BEGIN
-- Step 1: read (every other call to AddOrder should wait here until this procedure has finished
SELECT @TotalOrderAmount = sum(Amount) FROM Orders WHERE CustomerID = 5
-- Step 2: modify
SELECT @DiscountPct = CASE WHEN @TotalOrderAmount > 1000.00 THEN 0.10 ELSE 0.00 END
SELECT @Amount = 9.99 * (1 - @DiscountPct)
-- Step 3: write
INSERT INTO Orders(CustomerID, Amount) VALUES (5, @Amount)
END
我脑海中的第一件事当然是使用具有提升的隔离级别的事务:
SET TRANSACTION ISOLATION LEVEL REPEATBLE READ
BEGIN TRAN
-- Step 1
-- Step 2
-- Step 3
COMMIT TRAN
但这不会解决任何问题。假设2个连接在完全相同的时间执行该过程。步骤1将放置并保持一个shared_read锁,并且两个连接都将通过已经错误的第一步。但它变得更糟,因为表中有2个锁将在步骤3中更新,会出现死锁。
我不想将所有内容分组到一个语句中(如果这样可以解决任何问题),因为我的实际情况当然比示例更复杂。
我还想使用现代SQL Server的范围锁定而不是锁定整个表,以便只锁定该CustomerID的行。 最后,我不会乐观锁定,因此两个调用应该总是成功。
有没有人有这个问题的简单解决方案?
更新:
首先,似乎使用表提示UPDLOCK可以解决问题。例如:
BEGIN TRAN
-- Step 1: read or wait until other instance has finished
SELECT @TotalOrderAmount = sum(Amount) FROM Orders with (UPDLOCK, ROWLOCK) WHERE CustomerID = 5
-- Step 2: modify
SELECT @DiscountPct = CASE WHEN @TotalOrderAmount > 1000.00 THEN 0.10 ELSE 0.00 END
SELECT @Amount = 9.99 * (1 - @DiscountPct)
-- Step 3: write
INSERT INTO Orders(CustomerID, Amount) VALUES (5, @Amount)
COMMIT TRAN
最大的好处是只有CustomerID = 5的订单行才会被锁定,因此大多数通话都不会等待,因为它们会与不同的客户一起使用。
但是这种方法仍然存在一个主要缺点:对于新客户来说它根本不起作用,因为还没有行可以锁定。因此,具有相同新CustomerID(尚未订购)的2个并发调用将不会彼此等待。
所以除了UPDLOCK,ROWLOCK我还需要像
这样的东西像
这样的东西BEGIN TRAN
IF EXISTS(SELECT * FROM Orders WHERE CustomerID = 5)
SELECT @TotalOrderAmount = sum(Amount) FROM Orders with (UPDLOCK, ROWLOCK) WHERE CustomerID = 5
ELSE
SELECT @TotalOrderAmount = sum(Amount) FROM Orders with (UPDLOCK, TABLOCK) WHERE CustomerID = 5
但是在1声明中(因为IF EXISTS也需要并发证明)。 TABLOCK似乎也不是最好的解决方案,因为当选择新客户时,现有客户(获得ROWLOCK)也在等待TABLOCK的发布。这就是我在上面提到'新行锁'的原因。
答案 0 :(得分:1)
开始交易后,您可以在UPDLOCK
上使用XLOCK
/ SELECT
提示。
这样的事情。
样本表结构
CREATE TABLE Orders
(
OrderID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CustomerID INT NOT NULL,
Amount NUMERIC(18,2) NOT NULL
);
CREATE INDEX IDX_Cutomer_Orders ON Orders(CustomerID) INCLUDE(Amount);
INSERT INTO Orders VALUES(1,123.25),(1,55),(2,8765900),(7,900);
INSERT INTO Orders VALUES(5,123.25),(5,8765900);
<强> PROCEDURE 强>
CREATE PROCEDURE AddOrder
@CustomerID INT
AS
BEGIN
BEGIN TRANSACTION
DECLARE @TotalOrderAmount NUMERIC(18,2),@Amount NUMERIC(18,2),@DiscountPct NUMERIC(4,2)
-- Step 1: read (every other call to AddOrder should wait here until this procedure has finished
SELECT @TotalOrderAmount = SUM(Amount) FROM Orders WITH (UPDLOCK ,ROWLOCK)
WHERE CustomerID = @CustomerID
-- Step 2: modify
SELECT @DiscountPct = CASE WHEN @TotalOrderAmount > 1000.00 THEN 0.10 ELSE 0.00 END
SELECT @Amount = 9.99 * (1 - @DiscountPct)
WAITFOR DELAY '00:00:10'
-- Step 3: write
INSERT INTO Orders(CustomerID, Amount) VALUES (@CustomerID, @Amount)
SELECT * FROM Orders WHERE CustomerID = @CustomerID
COMMIT
END
此处,同时拨打EXEC AddOrder 1
会等待初始的commit
/ rollback
。
对EXEC AddOrder 1
和EXEC AddOrder 5
的通话将并行工作而不会相互阻挡。