SQL Server:在select中使用表锁提示以确保正确性?

时间:2017-03-26 16:25:41

标签: sql-server select hints

我有一个尝试应用DDD(域驱动设计)的项目。目前,我们有这样的事情:

begin tran
 try
  _manager.CreateNewEmployee(newEmployeeCmd);
  tran.Commit();
 catch
  rollback tran

在内部,CreateNewEmployee方法使用域服务来检查是否已有具有memberId的员工。这是一些伪代码:

void CreateNewEmployee(NewEmployeeCmd cmd)
  if(_duplicateMember.AlreadyRegistered(cmd.MemberId) )
    throw duplicate
  // extra stuff
  saveNewEmployee()
end

现在,最后,好像我们执行了以下SQL指令(再次使用pesudo代码):

begin sql tran
 select count(*) from table where memberId=@memberId and status=1 -- active
 --some time goes by
 insert into table ...
end

不,当我开始查看代码时,我注意到它使用的是默认的SQL Server锁定级别。在实践中,这意味着可能发生这样的事情:

--thread 1
(1)select ... --assume it returns 0
--thread 2
(2)select ... ---nothing found
(3)insert recordA
--thread 1
(4)insert record --some as before
(5) commit tran
--thread 1
(6) commit tran

所以,我们最终可能会重复记录。我已尝试使用事务级别,但我设法让它按预期工作的唯一方法是更改​​用于检查表中是否已有记录的选择。我最终使用了表锁提示,指示sql在事务结束前保持锁定。这是我在选择开始时设法获得锁定的唯一方法(更改其他隔离级别仍然不会执行我需要的操作,因为它们都允许选择运行)

所以,我最终使用了一个从开始到事务结束的表锁。实际上,这意味着步骤(2)将阻塞,直到线程1结束其工作。

这种情况是否有更好的选择(不依赖于使用索引)?

感谢。

路易斯

1 个答案:

答案 0 :(得分:1)

您需要在初始select上获得正确的锁定,您可以使用锁定提示with (updlock, serializable)来执行此操作。一旦你这样做,线程2将等待线程1完成,如果线程2在其where中使用相同的键范围。

您可以使用Sam Saffron upsert approach

例如:

create procedure dbo.Employee_getset_byName (@Name nvarchar(50), @MemberId int output) as
begin
  set nocount, xact_abort on;
  begin tran;
    select  @MemberId = Id
      from  dbo.Employee with (updlock, serializable) /* hold key range for @Name */
      where Name = @Name;
    if @@rowcount = 0 /* if we still do not have an Id for @Name */
    begin;
    /* for a sequence */
      set @MemberId = next value for dbo.IdSequence; /* get next sequence value */
      insert into dbo.Employee (Name, Id)
        values (@Name, @MemberId);
    /* for identity */
      insert into dbo.Employee (Name)
        values (@Name);
      set @MemberId = scope_identity();
    end;
  commit tran;
end;
go