选择或插入时SQL Server 2012潜在竞争条件

时间:2014-05-03 15:32:19

标签: sql sql-server sql-server-2012

我需要运行一个“获取”或“创建”记录的SQL Server查询。很简单,但我不确定这是否会产生任何竞争条件。

我已经阅读了一些关于插入/更新期间锁定的文章,例如: http://weblogs.sqlteam.com/dang/archive/2007/10/28/Conditional-INSERTUPDATE-Race-Condition.aspx,但我不确定这是否与我相关,因为我正在使用SQL Server 2012,而且我没有对修改本身的争论,只是确保我的记录“一次性创建” 。

另外,我将对Id列(不是标识列)有一个主键约束,所以我知道最坏的竞争条件只能创建一个错误,而不是无效的数据,但我也是不希望命令抛出错误。

有人可以说明我需要解决这个问题吗?或者我在想这个,我可以简单地做一些事情:

IF EXISTS(SELECT * FROM Table WHERE Id = @Id)
BEGIN
      SELECT Id, X, Y, Z FROM Table WHERE Id = @Id
END
ELSE
BEGIN
      INSERT INTO Table (Id, X, Y, Z)
      VALUES (@Id, @X, @Y, @Z);

      SELECT @Id, @x, @Y, @Z;
END

我已经在文档数据库领域呆了几年而且我的SQL非常生疏。

3 个答案:

答案 0 :(得分:3)

您的代码肯定有竞争条件。

一种方法是将条件合并为一个insert,但我不能100%确定这可以防止所有竞争条件,但这可能有效:

INSERT INTO Table(Id, X, Y, Z)
    SELECT @Id, @X, @Y, @Z
    WHERE NOT EXISTS (SELECT 1 FROM Table WHERE ID = @ID);

SELECT Id, X, Y, Z FROM Table WHERE ID = @Id;

问题在于重复检测期间的基础锁定机制与插入。您可以尝试使用更明确的锁定,但表锁定肯定会降低处理速度(要了解锁定,您可以转到文档或博客文章,例如this)。

我认为使这项工作始终如一的最简单方法是使用try / catch块:

BEGIN TRY
    INSERT INTO Table(Id, X, Y, Z)
        SELECT @Id, @X, @Y, @Z;
END TRY
BEGIN CATCH
END CATCH;

SELECT Id, X, Y, Z FROM Table WHERE ID = @Id;

即尝试insert。如果它失败(可能是因为重复的密钥,但你可以明确地检查它),那么忽略失败。之后返回id的值。

答案 1 :(得分:3)

GET的发生频率远高于CREATE,因此优化它是有意义的。

IF EXISTS (SELECT * FROM MyTable WHERE Id=@Id)
    SELECT Id, X, Y, Z FROM MyTable WHERE Id=@Id
ELSE
BEGIN
    BEGIN TRAN
        IF NOT EXISTS (SELECT * FROM MyTable WITH (HOLDLOCK, UPDLOCK) WHERE Id=@Id )
        BEGIN
        --  WAITFOR DELAY '00:00:10'
            INSERT INTO MyTable (Id, X, Y, Z) VALUES (@Id, @X, @Y, @Z)
        END
        SELECT Id, X, Y, Z FROM MyTable WHERE Id=@Id
    COMMIT TRAN
END

如果您的记录不存在,请开始显式交易。 需要HOLDLOCK和UPDLOCK(感谢@MikaelEriksson)。表提示在事务持续期间保持共享锁和更新锁。这样可以正确地序列化INSERT并可靠地避免竞争条件。

要验证,请取消注释WAITFOR并同时运行两个或多个查询。

答案 2 :(得分:0)

为什么你需要两个不同的SELECT

IF NOT EXIST (SELECT TOP 1 1 FROM Table WHERE Id = @Id)
    INSERT INTO Table(Id, X, Y, Z)
    VALUES (@Id, @X, @Y, @Z)

SELECT Id, X, Y, Z FROM Table WHERE ID = @Id

只要你在检查存在时没有指定WITH (NOLOCK),你应该没事。