我有一个在SQL Server 2008上运行的程序。有一个创建新货件的功能。创建货件所需的最小数据是idno,subno和class。主键是idno + idsub。我创建了一个存储过程来创建一个新的货件,如下所示
ALTER Proc [dbo].[spDP_CreateNewShipment](@Class char(10),@idno int = 0, @idsub smallint = 1)
as
If @idno = 0
Begin
Select @idno = Max(idno)+1 From Shipment
End
Insert Into Shipment
(idno,idsub, class)
Values (@idno,@idsub,@class)
如果我将@idno传递给0,它应该创建一个包含下一个可用idno的货件。这很有效,除非有时两个新货将获得相同的新idno。似乎时间必须完全相同,这应该几乎不可能,但它发生了。我能想到的另一个可能性是Insert可能会被缓冲,可能不会立即发生。我对SQL Server设置知之甚少。有没有人知道可能导致写入不立即发生的设置?
答案 0 :(得分:2)
简短回答是:在这种情况下你不需要它,而是使用identity
主键。
由于race condition
,您遇到了这个问题。生成@idno
之后,另一个线程也会生成相同的@idno
。然后两个线程都试图插入具有相同@idno
的行,因此第二个插入违反了primary key
唯一性规则。
Identity主键允许您只插入非Id-data-columns并将next-id-generation留给数据库。然后你可以问它是什么产生的。
对于insert
代码示例,请查看此SO answer。这样的事情:
if @idno = 0 begin
Insert Into Shipment(idsub, class)
Output inserted.idno -- it outputs to the client idno of the inserted row
Values (@idno,@idsub,@class)
end
else begin
-- @idno != 0 case
-- are you SURE you wanna insert with @idno generated on client?
-- if you HAVE TO, then you probably should use
-- GUID (aka uniqueidentifier) for idno column
-- instead of identity int
end
有关identity
列的详细信息,请查看msdn。简而言之,您声明此列的下一个值是基于建议seed
的某个初始值step
生成的。最常见的(seed, step)
是(1, 1)
,但您可以设置任何您想要的内容。
答案 1 :(得分:1)
将语句包装到transaction
并设置最高隔离级别。这样的事情。
ALTER Proc [dbo].[spDP_CreateNewShipment](@Class char(10),@idno int = 0, @idsub smallint = 1)
as
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
If @idno = 0
Begin
Select @idno = Max(idno)+1 From Shipment
End
Insert Into Shipment
(idno,idsub, class)
Values (@idno,@idsub,@class)
COMMIT TRANSACTION
答案 2 :(得分:0)
在交易中执行操作可能会更好;
begin transaction
If @idno = 0
Begin
Select @idno = Max(idno)+1 From Shipment
End
Insert Into Shipment (idno,idsub, class) Values (@idno,@idsub,@class)
commit transaction