我有一个名为 tblOrderNumber 的表,它有 1 行和 1 列。此表存储我的电子商务网站的下一个订单号。同一个订单号不被多次使用是绝对重要的。目前团队正在使用这个存储过程,它似乎工作正常:
我的问题是 UPDLOCK 能保证这一点吗?我原以为 SELECT 也需要读锁(据我所知,在不太可能的情况下,两个订单彼此相隔一毫秒,而第一个订单在第二个订单执行 SELECT 之前没有执行更新,据我所知此过程中没有读锁)?
DECLARE @NextOrderNumber INT
BEGIN TRANSACTION
SELECT @NextOrderNumber = NextOrderNumber
FROM tblOrderNumber (UPDLOCK)
UPDATE tblOrderNumber
SET NextOrderNumber = NextOrderNumber + 1
COMMIT
SELECT @NextOrderNumber
--客户实现(UPDLOCK或SERIALIZABLE在这里会更好,因为我认为我们不需要锁定整个表吗?)
UPDATE dbo.tblOrderNumber WITH (SERIALIZABLE) SET @NextOrderNumber = NextOrderNumber, NextOrderNumber = NextOrderNumber + 1; WHERE CustomerId=@CustomerId
我正在使用 SQL Server 2014,但我很快将更改为 SQL Azure。
简答
更新锁就足够了,但您可以通过以下方式更简单地实现您想要的:
如果
WITH (SERIALIZABLE)
CustomerID 上存在唯一索引,则提示不是严格要求的。更长的答案
在提供的代码中更新锁定提示就足够了。一次只有一个事务可以获取资源的更新 (U) 锁,并且更新锁一直保持到事务结束。在进行更改之前,更新锁将转换为排他 (X) 锁。排他锁也保持到事务结束。
因此,在读取时获取更新锁可提供您正在寻找的并发保证。需要明确的是:一旦获取更新锁(通过选择),在第一个事务提交或中止之前,没有其他事务可以获取同一资源上的更新锁。
您还可以在没有提示的情况下在
SERIALIZABLE
隔离级别运行事务(或单个更新语句) 。毕竟,您正在寻找的保证是事务应该根据可序列化的时间表执行。如果您不习惯依赖锁定内部知识、指定所需的隔离级别并让 SQL Server 处理细节,可能会更简单。显然,您还需要代码来处理错误或可能的死锁。我假设您从示例中省略了这一点。一个带大括号的方法也
SET XACT_ABORT ON
将确保该过程几乎所有可能的错误都会中止事务,而不是默默地继续。也可以使用应用程序锁(使用
sp_getapplock
和sp_releaseapplock
)手动编写一个健壮的锁定实现,但老实说,使用内置的可序列化隔离级别可能是最简单的。更多信息请看我的系列文章: