如何解决 SQL 服务器存储过程中的并发插入问题

How to solve concurrency Insert issue in SQL Server stored procedure

我有一个将 @id 作为输入参数的存储过程。 Table StudentId_ColumnCount_Column 上有主键。如果给定 id 的 table 中存在任何记录,那么我 select 来自 table 的最大值 Count_Column 并通过递增最大值 [=16] 插入新行=] by 1 else 零值。

我从 WCF 服务中的 ado.net 代码调用此存储过程,而此服务是从 asp.net Web 应用程序调用的。

这个存储过程在正常情况下工作正常,但是当多个用户同时调用它时,会发生主键冲突问题,同样的情况我也通过使应用程序多线程来重现。我知道这个问题与并发有关,最初我在 select 查询中使用 with(nolock),但我现在已经删除了它。

我在某处读到通过设置事务隔离级别可以解决这个问题,但是当我尝试时我遇到了一些回滚事务异常。

请让我知道这个问题的任何有效解决方案。

declare @iCount = 0;

if exists(select 'x' from Student with(nolock) where Id_Column = @iId)
begin
    set @iCount = (select max(Count_Column) 
                   from Student
                   where Id_Column = @iId)
end

insert into Student
values(@id, @iCount + 1);

第二种解决方案:

begin try
set transaction isolation level serializable
begin transaction
    declare @iCount = 0;

    if exists(select 'x' from from Student with(nolock) where Id_Column = @iId)
    begin
        set @iCount = (select max(Count_Column) 
                       from Student
                       where Id_Column = @iId)
    end

    insert into Student
    values(@id, @iCount + 1);

    commit transaction
end try
begin catch
    rollback transaction
end catch

有一个故障保护策略:SERIALIZABLE隔离并在出现死锁时重试。 SERIALIZABLE 的定义类似于串行执行,排除了所有竞争条件。

我不知道你为什么在那里捕获异常。所完成的只是抑制错误。

试试……

BEGIN TRY
  BEGIN TRANSACTION;
     DECLARE @iCount INT;

    IF EXISTS(SELECT 1 FROM Student WITH(UPDLOCK,HOLDLOCK) WHERE Id_Column = @iId)
    BEGIN

      select @iCount = ISNULL(max(Count_Column), 0) + 1 
      from Student WITH(UPDLOCK,HOLDLOCK) where Id_Column = @iId

         insert into Student
         values(@id, @iCount);
    END
  COMMIT TRANSACTION;
END TRY

BEGIN CATCH

 IF (@@TRANCOUNT <> 0)
     ROLLBACK TRANSACTION;

END CATCH

重要提示

你真的应该在这里使用 Identity 列来处理自动增量值。如果您使用的是 sql server 2012 或更高版本,您还可以选择使用 Sequence Object 也是一个 auto-increment 。

Oracle 有序列,MS 紧随其后的是 Sql Server 2012,它们的实现调用序列对象,这不是 table 特定的。

CREATE SEQUENCE   
Schema.MySequence
AS int
INCREMENT BY 1 ;

然后您可以按如下方式使用它:

DECLARE @iCount int ;
SET @iCount = NEXT VALUE 
FOR Schema.MySequence;

这与Oracle 中的nextval 类似,将确保唯一性。唯一的问题是,在 rolledback/failed 笔交易的情况下,您是否会接受计数序列中的空白...