问题描述
尝试在没有身份密钥的表中执行多个连续插入。
唯一ID来自名为GetNextObjectId
的过程。 GetNextObjectId
是没有输出参数和返回值的存储过程。
而是选择一个top 1 int
字段。
试过这个:
declare @nextid int;
exec @nextid = GetNextObjectId 1; insert into MyTable values (@nextid,...);
exec @nextid = GetNextObjectId 1; insert into MyTable values (@nextid,...);
go
然后这样:
declare @nextid int; exec @nextid = GetNextObjectId 1; insert into MyTable values (@nextid,...);
go
declare @nextid int; exec @nextid = GetNextObjectId 1; insert into MyTable values (@nextid,...);
go
但是插入中的@nextid
的值始终是相同的。
问题
在不修改存储过程的情况下刷新此变量的值的正确方法是什么?
某些上下文
这个问题的根源是我在寻找一种使用现有存储过程将测试数据插入表中的快速方法,而不是设法这样做。问题仅与以下事实有关:变量的值在语句之间未得到更新,与在表中插入数据的正确方法无关。这不是生产代码。据我了解,使用实体框架和并发代码也需要这样的过程。由于身份存在问题,因此每个线程在保存上下文之前都会获得自己的ID,如下所示:
// Receive a batch of objects and persist in database
// using Entity Framework.
foreach (var req in requests)
{
// some validation
ctx.MyTable.Add(new Shared.Entities.MyTableType
{
Id = ctx.GetNextObjectId(Enums.ObjectTypes.MyTableType),Code = req.Code,Name = req.Name
});
// save to database every 1000 records
counter++;
if (counter % 1000 == 0)
{
ctx.SaveChanges();
counter = 0;
}
}
// save remaining if any
ctx.SaveChanges();
该过程将执行以下操作:
BEGIN TRAN T1
UPDATE [dbo].[ObjectsIds] WITH (ROWLOCK)
SET NextId = NextId + Increment
WHERE ObjectTypeId = @objectTypeId
SELECT NextId
FROM [dbo].[ObjectsIds]
WHERE ObjectTypeId = @objectTypeId
COMMIT TRAN T1
解决方法
这种方法有很多问题,以至于评论还不够。
首先,存储过程返回一个偶尔使用的整数。使用时,这应该是指示成功或失败的状态值。没有要求,但是即使如此,Microsoft也是如此在documentation中描述值。听起来您的存储过程只是在运行查询,甚至没有返回状态值。
第二,为此目的使用存储过程意味着您具有比赛条件。这意味着即使代码似乎有效,它对于并发插入也可能无效。
第三,您的代码要求在每个插入操作中都调用存储过程。如果您实际上关心值,那么这似乎非常危险。
第四,您应该使用唯一索引或约束来验证数据完整性,以防止后续插入的具有相同值。
什么是正确的解决方案?好吧,最好的解决方案是简单地用identity()
列枚举每一行。如果您需要按列进行特定计数,则可以在查询期间进行计算。
如果这不能满足您的需求(尽管对我而言一直足够好),您可以编写触发器。编写触发器时,您需要小心锁定表,以确保并发插入不会产生相同的值。这可能暗示使用多种序列之类的机制。或者它可能建议将表聚集在各个组之间。
短消息:触发器是执行所需操作的机制(在DML操作期间影响数据)。存储过程不是机制。