【发布时间】:2016-01-26 09:22:06
【问题描述】:
我有一个表,用于创建具有唯一键的锁,以控制在多个服务器上执行关键部分,即一次只有一个来自所有 Web 服务器的线程可以进入该关键部分。
锁机制首先尝试向数据库中添加一条记录,如果成功则进入该区域,否则等待。当它退出临界区时,它会从表中删除该键。为此,我有以下程序:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRANSACTION
DECLARE @startTime DATETIME2
DECLARE @lockStatus INT
DECLARE @lockTime INT
SET @startTime = GETUTCDATE()
IF EXISTS (SELECT * FROM GuidLocks WITH (TABLOCKX, HOLDLOCK) WHERE Id = @lockName)
BEGIN
SET @lockStatus = 0
END
ELSE
BEGIN
INSERT INTO GuidLocks VALUES (@lockName, GETUTCDATE())
SET @lockStatus = 1
END
SET @lockTime = (SELECT DATEDIFF(millisecond, @startTime, GETUTCDATE()))
SELECT @lockStatus AS Status, @lockTime AS Duration
COMMIT TRANSACTION GetLock
所以我在表上执行SELECT 并使用TABLOCKX 和HOLDLOCK,所以我在整个表上获得了排他锁并将其保留到事务结束。然后根据结果,我要么返回失败状态(0),要么创建一个新记录并返回(1)。
但是,我不时收到此异常,我只是不知道它是如何发生的:
System.Data.SqlClient.SqlException:违反主键约束“PK_GuidLocks”。无法在对象“dbo.GuidLocks”中插入重复键。重复键值为 (XXXXXXXXX)。该语句已终止。
知道这是怎么发生的吗?两个线程怎么可能在同一张表上获得了排他锁,并试图同时插入行?
更新:看来读者可能还没有完全理解我这里的问题,所以我想详细说明一下:我的理解是使用TABLOCKX获得了一个独占锁桌子。我还从文档中了解到(我可能会弄错),如果我使用 HOLDLOCK 语句,那么锁将一直保持到事务结束,在这种情况下,我假设(显然我的假设是错误的,但是这就是我从文档中了解到的)是由BEGIN TRANSACTION 语句发起并由COMMIT TRANSACTION 语句结束的外部事务。所以我在这里理解的方式是,当 SQL Server 到达具有 TABLOCKX 和 HOLDLOCK 的 SELECT 语句时,它会尝试获取整个表的排他锁,并且在执行COMMIT TRANSACTION 之前不会释放它。如果是这样,两个线程怎么会同时尝试执行相同的 INSERT 语句?
【问题讨论】:
-
将您的隔离级别设置为 SERIALIZABLE 而不是 READ COMMITTED;这就是对一系列事务强制执行可序列化的方式。
-
@PieterGeerkens,但这是我在 HOLDLOCK 的文档中找到的:“相当于 SERIALIZABLE。有关更多信息,请参阅本主题后面的 SERIALIZABLE。HOLDLOCK 仅适用于表或视图它是指定的,并且仅在使用它的语句定义的事务期间。HOLDLOCK 不能在包含 FOR BROWSE 选项的 SELECT 语句中使用。"
-
以防万一您不知道它存在,您可能需要查看 sp_getapplock:msdn.microsoft.com/en-us/library/ms189823.aspx
标签: sql-server tsql