如何解决 SQL 服务器存储过程中的并发插入问题
How to solve concurrency Insert issue in SQL Server stored procedure
我有一个将 @id
作为输入参数的存储过程。 Table Student
在 Id_Column
和 Count_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 笔交易的情况下,您是否会接受计数序列中的空白...
我有一个将 @id
作为输入参数的存储过程。 Table Student
在 Id_Column
和 Count_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 笔交易的情况下,您是否会接受计数序列中的空白...