sup*_*fly 0 sql t-sql sql-server
尝试在没有标识键的表中执行多个连续插入。唯一 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
Run Code Online (Sandbox Code Playgroud)
然后这个:
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
Run Code Online (Sandbox Code Playgroud)
但是@nextid插入中的值始终相同。
问题
在不修改存储过程的情况下刷新此变量的值的正确方法是什么?
一些上下文
这个问题的起源是我正在寻找一种使用现有存储过程在表中插入测试数据的快速方法,而不是设法做到这一点。问题仅涉及变量的值不会在语句之间更新这一事实,而不涉及在表中插入数据的正确方法。这不是生产代码。另外据我所知,使用带有并发代码的实体框架需要这样的过程;由于 Identity 存在问题,每个线程在保存上下文之前都会获得自己的 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();
Run Code Online (Sandbox Code Playgroud)
该程序执行以下操作:
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
Run Code Online (Sandbox Code Playgroud)
这种方法有很多问题,评论是不够的。
首先,存储过程返回一个偶尔使用的整数。使用时,这应该是指示成功或失败的状态值。没有要求,但这就是 Microsoft 对文档中价值的描述。听起来您的存储过程只是在运行查询,甚至没有返回状态值。
其次,为此目的使用存储过程意味着您有竞争条件。这意味着即使代码看起来有效,它也可能不适用于并发插入。
第三,您的代码需要在每次插入时调用存储过程。如果您真的关心价值,那似乎非常危险。
第四,您应该使用唯一索引或约束来验证数据完整性,以防止具有相同值的后续插入。
什么是正确的解决方案?好吧,最好的解决方案是简单地用一identity()列枚举每一行。如果您需要按列进行特定计数,则可以在查询期间进行计算。
如果这不能满足您的需求(尽管它对我来说一直足够好),您可以编写一个触发器。在编写触发器时,您需要小心锁定表以确保并发插入不会产生相同的值。这可能建议使用诸如多序列之类的机制。或者它可以建议将表格围绕组进行聚类。
短消息:触发器是做你想做的事情的机制(在 DML 操作期间影响数据)。存储过程不是机制。