我有一个存储过程实际上是尝试在表中进行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
答案 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,而这绝不会有任何乐趣。