当使用相同参数同时调用时,存储过程会抛出违反PRIMARY KEY约束

时间:2016-06-13 13:14:41

标签: sql-server

CREATE TABLE dbo.SomeTable
(
    ID int NOT NULL,
    SomeText varchar(10) NOT NULL
)  ON [PRIMARY]
GO

ALTER TABLE dbo.SomeTable 
    ADD CONSTRAINT PK_SomeTable 
    PRIMARY KEY CLUSTERED (ID)
         WITH(STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
              ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE PROCEDURE [dbo].[InsertSomeText]
    @ID int,
    @SomeText varchar(10)
AS
BEGIN
    BEGIN TRANSACTION

    DELETE FROM SomeTable WHERE ID = @ID

    INSERT INTO SomeTable (ID, SomeText)
    VALUES (@ID, @SomeText)

    COMMIT TRANSACTION      
END

上述存储过程有时会抛出错误

  

违反PRIMARY KEY约束

同时使用相同的@ID值进行调用。

删除/插入操作在事务内部,所以我认为这将是一个原子操作,但仍有一段时间它会抛出异常。

2 个答案:

答案 0 :(得分:2)

实际上很少有避免竞争条件的万无一失的方法,除非你指定锁定级别,否则使用事务不是其中之一。不幸的是,我从未见过使用删除和插入管理的upsert,因此我无法找到任何文档或测试来显示竞争条件的发生方式和原因。

我建议只使用线程安全的MERGE WITH (HOLDLOCK),而不是修复一个有点奇怪的方法(删除/插入):

CREATE PROCEDURE [dbo].[InsertSomeText]
    @ID int,
    @SomeText varchar(10)
AS
BEGIN
    MERGE dbo.SomeTable WITH (HOLDLOCK) AS t
    USING (VALUES (@ID, @SomeText)) s (ID, SomeText)
        ON s.ID = t.ID
    WHEN MATCHED THEN
        UPDATE SET SomeText = s.SomeText
    WHEN NOT MATCHED THEN 
        INSERT (ID, SomeText) VALUES (s.ID, s.SomeText);
END;

答案 1 :(得分:0)

尝试将ISOLATION LEVEL更改为SERIALIZABLE(最具限制性)以避免任何并发问题。 详情请参考MSDN: https://msdn.microsoft.com/en-IN/library/ms173763.aspx