SQL表锁定

SQL表锁定,sql,sql-server,tsql,Sql,Sql Server,Tsql,我有一个关于内部应用程序的SQL Server锁定问题。应用程序接受数据提交并将其持久化到SQL Server表中。每一次提交都会分配一个与表中标识字段无关的特殊目录号,标识字段是一个字母数字顺序号。这些数字是从另一个表中提取的,不会在运行时生成。因此,步骤是 将数据插入提交表 抓取下一个未分配的目录 目录表中的编号 将目录号指定给 提交表格中的提交 所有这些步骤都在同一存储过程中顺序进行 它的速度很快,但有时我们设法在同一秒钟内收到两份提交,并且他们都被分配了相同的目录号,这导致我们公司在一小

我有一个关于内部应用程序的SQL Server锁定问题。应用程序接受数据提交并将其持久化到SQL Server表中。每一次提交都会分配一个与表中标识字段无关的特殊目录号,标识字段是一个字母数字顺序号。这些数字是从另一个表中提取的,不会在运行时生成。因此,步骤是

将数据插入提交表 抓取下一个未分配的目录 目录表中的编号 将目录号指定给 提交表格中的提交 所有这些步骤都在同一存储过程中顺序进行

它的速度很快,但有时我们设法在同一秒钟内收到两份提交,并且他们都被分配了相同的目录号,这导致我们公司在一小段时间内出现了《启示录》的本地化版本


我们可以做些什么来限制目录号的过度分配

获取下一个目录号时,使用行锁定来保护找到它和将其标记为正在使用之间的时间,例如:

set transaction isolation level REPEATABLE READ
begin transaction
select top 1 @catalog_number = catalog_number
  from catalog_numbers with (updlock,rowlock)
  where assigned = 0
update catalog_numbers set assigned = 1 where catalog_number = :catalog_number
commit transaction

您可以使用标识字段生成目录编号,这样您就可以安全地创建和获取编号:

insert into Catalog () values ()
set @CatalogNumber = scope_identity()
scope_identity函数将返回在同一会话中创建的最后一条记录的id,因此单独的会话可以同时创建记录,并且仍然以正确的id结束


如果不能使用标识字段来创建目录号,则必须使用事务来确保可以确定下一个编号并创建它,而无需另一个会话访问该表。

我喜欢araqnid的响应。您还可以在提交表上使用insert触发器来完成此操作。触发器将在insert的范围内,并且您将有效地嵌入逻辑以在触发器中分配catalog_编号。只是想把你的选择放在这里。

这是一个简单的解决方案。没有比赛条件。没有来自限制性事务隔离级别的阻塞。不过,除了t-SQL之外,在SQL方言中可能不起作用

我假设他们是某种外力在起作用,使您的目录号表中填充了未分配的目录号

这种技术应该适用于您:只需执行检索值的相同类型的联锁更新,例如:

update top 1 CatalogNumber
set in_use            = 1 ,
    @newCatalogNumber = catalog_number
from CatalogNumber
where in_use = 0
无论如何,下面的存储过程只是在每次执行时勾选一个数字,然后返回上一个。如果您想要fancier值,请添加一个计算列,将选择的变换应用于递增值以获得所需的值

drop table dbo.PrimaryKeyGenerator
go
create table dbo.PrimaryKeyGenerator
(
  id            varchar(100) not null ,
  current_value int          not null default(1) ,

  constraint PrimaryKeyGenerator_PK primary key clustered ( id ) ,

)
go
drop procedure dbo.GetNewPrimaryKey
go
create procedure dbo.GetNewPrimaryKey

  @name varchar(100)

as

  set nocount                 on
  set ansi_nulls              on
  set concat_null_yields_null on
  set xact_abort              on

  declare
    @uniqueValue int

  --
  -- put the supplied key in canonical form
  --
  set @name = ltrim(rtrim(lower(@name)))

  --
  -- if the name isn't already defined in the table, define it.
  --
  insert dbo.PrimaryKeyGenerator ( id )
  select id = @name
  where not exists ( select *
                     from dbo.PrimaryKeyGenerator pkg
                     where pkg.id = @name
                   )

  --
  -- now, an interlocked update to get the current value and increment the table
  --
  update PrimaryKeyGenerator
  set @uniqueValue  = current_value ,
      current_value = current_value + 1
  where id = @name

  --
  -- return the new unique value to the caller
  --
  return @uniqueValue
go
要使用它:

声明@pk int exec@pk=dbo.GetNewPrimaryKey'foobar' 选择@pk


对其进行修改以返回结果集或通过输出参数返回值非常简单。

存储过程中的所有三个步骤都封装在事务中吗?事务上设置的隔离级别是什么?如果分配的目录号中存在漏洞,这是否也是一个启示?i、 e.如果T1被分配了一个目录号,然后出现了一个错误,然后回滚。如果该目录号被跳过,这是一个问题吗?很抱歉,我被叫走了,无法监控我自己的问题。因此,1不,它们当前不在事务中,只是在同一存储过程中一个接一个地执行。2个缺口并不理想,但它们离世界末日1号相去甚远。行锁定与事务无关,它根本不会保护目录号。没有什么能阻止两个会话在其中一个会话使用相同的目录号之前选择相同的目录号。@Guffa updlock阻止了这一点。使现代化TOP和OUTPUT可以做到这一点,而不需要进行任何选择。@Martin:updlock将表锁定到事务结束。由于没有跨两个查询的事务,它们有自己的事务,因此事务和锁在第二个查询开始之前结束。是的,它们应该在事务内部运行。当然所有重要的更新都应该在事务内部运行。这有点苛刻。设置REPEATABLE READ(可重复读取)只是在应用程序中引入了一个瓶颈。+1这显然是明智的做法,如果你有重写的特权,会话不会只锁定当前行,允许创建新行吗?@ispiro:如果你使用一个标识字段,如果不同的会话同时创建一条记录就没有问题,您可以获取会话中创建的id。如果您必须先确定id,然后再执行插入,则需要一个事务,以便您可以让其他会话等待,直到您执行了这两个步骤。谢谢。哎呀,我是说交易。很抱歉我说的是第二个不使用标识的选项——事务真的会有帮助吗?我认为事务不会锁定整个表-因此两个并发操作可能会同时读取,并决定使用相同的id。如果我错了,请纠正我。“我是一个相当初级的人。@ispiro:如果我没有充分说明这一点,我很抱歉。”。你必须选择一个交易l evel提供对表的独占访问,否则您将得到您描述的问题。