插入时选择给出重复键违规

时间:2018-04-25 14:27:16

标签: sql-server concurrency

我有一个存储过程实际上是尝试在表中进行upsert。请考虑以下事项:

create table dbo.myTable (
   id int not null constraint PK_myTable primary key (id),
   payload varchar(100)
);

在存储过程中,我填充的临时表与具有用户输入的表具有相同的结构,然后尝试以下操作:

insert into dbo.myTable
   (id, payload)
select source.id, source.payload
from #temp as source
left join dbo.myTable as target
   on source.id = target.id
where target.id is null;

当我单独测试它时(即只是上面的语句,包含在显式事务中;下面的测试工具),运行第二个实例将等待一个键锁。这就是我的期望。但是,我所看到的是偶尔在负载下我得到重复的密钥错误。怎么可能?

我知道我有几种解决方法。我可以将IGNORE_DUP_KEY放在主键约束上。我也可以在try中包装insert语句并吞下错误。而且,老实说,我将探索这些选择。但我想了解第二笔交易如何获得插入绿灯。

测试工具

insert into #temp 
   (id, payload) 
values 
   (1, 'test');

begin transaction
go
    insert into dbo.myTable
    (id, payload)
    select source.id, source.id
    from #temp as source
    left join dbo.myTable as target
        on source.id = target.id
    where target.id is null

3 个答案:

答案 0 :(得分:0)

如果您想了解,那么您可以轻松地手动获取此错误。打开SSMS。在那里创建简单的表,例如:

CREATE TABLE test
(
 id int not null primary key
)

现在打开交易并进行插入:

BEGIN TRANSACTION
insert into test values(1); -- do not commit

然后在SSMS中打开新窗口并在那里重复相同的声明:

insert into test values(1); 

现在回到第一个窗口并提交事务。在第二个窗口中,您将获得:

  

Msg 2627,Level 14,State 1,Line 1违反PRIMARY KEY   约束'PK__test__3213E83F2818DDD3'。无法插入重复键   在对象'dbo.test'中。重复键值为(1)。

我认为解决该问题的最恰当方法是使用适当的隔离级别。

答案 1 :(得分:0)

Ben,显式事务以BEGIN开头,后跟COMMIT或ROLLBACK,两者都不存在。在存储过程中包含GO也是不合适的。请告诉我们为什么需要在#temp表中插入单个记录然后将其移动到myTable?在过程中提供参数@Id和@payload之后,您可能想尝试类似以下内容:

; MERGE dbo.myTable AS目标 使用(SELECT @ Id,@ payload)AS源(Id,Payload) ON(target.Id = source.Id) 当没有匹配时  INSERT(Id,Payload)  VALUES(source.Id,source.Payload);

请注意,MERGE语句需要前面的" ; "

不需要显式交易。

答案 2 :(得分:0)

您可以使用SEQUENCE对象来避免主键问题。

我现在不确定你在哪里创建你的id值,但是如果你创建一个SEQUENCE,比如sqPayload,那么无论你在哪里分配那个id号,都要调用NEXT VALUE FOR sqPayload,这些id将保持顺序和唯一,无论同时写多少笔交易,你都不会违反主键。

你可能想要做一个BIGINT SEQUENCE。如果你已经遇到并发问题,那么你可能会烧掉所有的INT,而这绝不会有任何乐趣。