2015-10-05 18 views
1

我有一个简单的getNextID过程,用于检索表中的id值并将该值递增1.它是在考虑到一些多线程的情况下构建的,但似乎UPDLOCK在该程序实际上并没有像预期的那样使线程安全,我试图理解为什么。这个想法是,初始选择期间的UPDLOCK会阻止任何其他线程执行该选择,直到该过程底部的更新完成;然而,这似乎并不是这种情况,因为当两个线程同时触发时,我得到重复值。T-SQL函数在UPDLOCK之后不是线程安全的

阅读一些其他线程之后,我认为可能发生的是UPDLOCK阻止其他线程更新行,但不阻止他们更新之前执行初始选择。因此,两个线程都执行相同的select(检索相同的值),则线程2正在等待线程1更新,然后线程2将该行更新为相同的值。我理解锁正在做什么?实现线程验证的正确方法是将它全部包装在BEGIN/COMMIT TRANSACTION中吗?

CREATE PROCEDURE getNextID (
    @NextNumber int OUTPUT 
    ,@id_type VARCHAR(20) 
    ) 
AS 
BEGIN 

    SELECT @NextNumber = (last_used_number + 1) 
    FROM its_id_sequence WITH (UPDLOCK) 
    WHERE id_type = @id_type 

    UPDATE its_id_sequence 
    SET last_used_number = @NextNumber 
    WHERE id_type = @id_type 
END 

谢谢!

+1

无论何时你试图推出自己的身份,你都在为一场失败的战斗而战。围绕这一点有很多挑战。为什么不使用已经完全实用的身份属性?如果你不喜欢差距(当然这是完全正常的),如果你在2012+以上,你可以改用序列。 –

+0

@SeanLange:我认为一个序列不能保证无间隙。 _Maybe_如果您在创建序列时指定了“no cache”,但会影响性能。我从来没有任何人能够合理地解释序列中没有空白的业务需求。 –

+0

@BenThul序列本身是无缝的。但是,如果您将它用作主键,只要删除一行,它就会以间隙结束。 OP在这里所做的基本上与已经构建的序列是一样的。 –

回答

1

另一种方法是稍微使用不同版本的更新查询,并将整个事件显示为一个事务。

CREATE PROCEDURE getNextID 
    @NextNumber INT   OUTPUT 
    ,@id_type VARCHAR(20) 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DECLARE @NextValue TABLE (NextNumber int); 

BEGIN TRANSACTION; 

    UPDATE its_id_sequence 
     SET last_used_number = ISNULL(@NextNumber, 0) + 1 
    OUTPUT inserted.last_used_number INTO @NextValue(NextNumber) 
    WHERE id_type = @id_type 

    SELECT @NextNumber = NextNumber FROM @NextValue 

COMMIT TRANSACTION; 

END 

因此,您先更新而不读取该值,并且一旦更新,然后获取该值并使用它。

+0

我从来没有见过OUTPUT插入的语法。感谢那!所以使这个线程安全的最好方法是在事务中执行更新和选择。只是为了我的理解,假设事务从一个select查询开始,然后进行更新(所以类似于SELECT(id + 1),然后用UPDATE ID SET id =(id + 1))结束。线程2是否能够在提交线程1之前执行选择,或线程2是否会在开始自己的事务之前等待整个线程1事务完成? – Moose