Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sql-server-2005/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 如何在SQLServer2005中保证事务完整性_Sql Server_Sql Server 2005_Concurrency_Locking_Transactions - Fatal编程技术网

Sql server 如何在SQLServer2005中保证事务完整性

Sql server 如何在SQLServer2005中保证事务完整性,sql-server,sql-server-2005,concurrency,locking,transactions,Sql Server,Sql Server 2005,Concurrency,Locking,Transactions,乍一看,我有一个非常简单的问题。我希望能够获得带有前缀的唯一键值。我有一个包含“Prefix”和“Next_Value”列的表 因此,您可能认为您只需启动一个事务,从该表中获取下一个值,增加表中的下一个值并提交,将前缀连接到该值,就可以得到一系列唯一的字母数字键 然而,在负载下,随着各种服务器通过ADO.NET访问这个存储过程,我发现它会不时地向不同的客户端返回相同的密钥。当密钥用作主键时,这会导致错误 我天真地假设BEGIN TRAN…COMMIT TRAN确保了范围内数据访问的原子性。在研究

乍一看,我有一个非常简单的问题。我希望能够获得带有前缀的唯一键值。我有一个包含“Prefix”和“Next_Value”列的表

因此,您可能认为您只需启动一个事务,从该表中获取下一个值,增加表中的下一个值并提交,将前缀连接到该值,就可以得到一系列唯一的字母数字键

然而,在负载下,随着各种服务器通过ADO.NET访问这个存储过程,我发现它会不时地向不同的客户端返回相同的密钥。当密钥用作主键时,这会导致错误

我天真地假设BEGIN TRAN…COMMIT TRAN确保了范围内数据访问的原子性。在研究这一点时,我发现了事务隔离级别,并添加了SERIALIZABLE作为最严格的限制—毫无乐趣

  Create proc [dbo].[sp_get_key] 
  @prefix nvarchar(3)
  as
  set tran isolation level SERIALIZABLE
  declare       @result nvarchar(32)  

  BEGIN TRY
      begin tran

      if (select count(*) from key_generation_table where prefix = @prefix) = 0 begin
         insert into key_generation_table (prefix, next_value) values (@prefix,1)
      end

      declare @next_value int

      select @next_value = next_value
      from key_generation_table
      where prefix = @prefix

      update key_generation_table
      set next_value = next_value + 1 
      where prefix = @prefix

      declare @string_next_value nvarchar(32)
      select @string_next_value = convert(nvarchar(32),@next_value)

      commit tran

      select @result = @prefix + substring('000000000000000000000000000000',1,10-len(@string_next_value)) + @string_next_value

      select @result

  END TRY
   BEGIN CATCH
        IF @@TRANCOUNT > 0 ROLLBACK TRAN

        DECLARE @ErrorMessage NVARCHAR(400);
        DECLARE @ErrorNumber INT;
        DECLARE @ErrorSeverity INT;
        DECLARE @ErrorState INT;
        DECLARE @ErrorLine INT;

        SELECT @ErrorMessage = N'{' + convert(nvarchar(32),ERROR_NUMBER()) + N'} ' + N'%d, Line %d, Text: ' + ERROR_MESSAGE();
        SELECT @ErrorNumber = ERROR_NUMBER();
        SELECT @ErrorSeverity = ERROR_SEVERITY();
        SELECT @ErrorState = ERROR_STATE();
        SELECT @ErrorLine = ERROR_LINE();
        RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)


    END CATCH  
这是密钥生成表

CREATE TABLE [dbo].[Key_Generation_Table](
        [prefix] [nvarchar](3) NOT NULL,
       [next_value] [int] NULL,
  CONSTRAINT [PK__Key_Generation_T__236943A5] PRIMARY KEY CLUSTERED 
  (
    [prefix] ASC
  )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,      
      ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
  ) ON [PRIMARY]

跳出框框思考,您是否可以只向具有自动增量id的表中添加一行,然后使用该id?这保证在负载下是唯一的。然后可以删除该行(这样就不会使表无限增长)

要回答您关于发生了什么的问题

可序列化

最严格的隔离级别。使用它时,无法出现幻象值。它防止其他用户在事务完成之前更新或向数据集中插入行

此机制旨在防止的问题与您遇到的问题不同


如果你想使用上面概述的方法,你应该获得关键区域的独占锁。

在你的If块上有一个竞争条件。两个请求同时输入一个新前缀,两个请求都可以通过if块。您应该将其改为始终插入到表中,但在insert的where子句中进行检查以确保它不存在。另外,我建议使用Exists而不是count(*)=0。一旦sql找到一行,它就可以停止查找

同样的事情也可能发生在select上,您可以让两个线程都选择相同的值,然后一个线程在等待更新时被阻塞,但当它返回时,它将返回旧id

修改逻辑以首先更新行,然后获取更新后的值

   update key_generation_table          
     set next_value = next_value + 1
     where   prefix = @prefix

       select @next_value = next_value -1         
       from key_generation_table          
       where prefix = @prefix          
我还将考虑使用ouput语句,而不是执行第二个select语句

编辑 我可能会将此更改为使用自SQL2005上的YRU以来的输出:

declare @keyTable as table  (next_value int)

UPDATE key_generation_Table
set next_value=next_value+1
OUTPUT DELETED.next_value into @keyTable(next_value)
WHERE prefix=@prefix

/* Update the following to use your formating */
select next_value from @keyTable 
试着暗示一下


键生成表理想情况下仅与此特定的存储过程一起使用。否则UPDLOCK会增加死锁的可能性。

Serializable保留锁,但允许读取。因此,在中间负载下的选择/更新可以在并行地非常快地调用PRO时给出相同的结果。 我想

如果您这样做,使用有效的语法,您可以组合2。 tablock确保整个桌子都被锁定。这与并发的可序列化不同。。tablock是粒度。 另外,你假设钥匙在那里。。如果需要,在后面添加缺少的前缀

update
    key_generation_table WITH (TABLOCK)
set
    @next_value = next_value, next_value = next_value + 1 
where
    prefix = @prefix

if @@ROWCOUNT = 0
begin
    set @next_value  = 1
    insert into key_generation_table (prefix, next_value) values (@prefix, 1)
end
select @string_next_value = convert(nvarchar(32),@next_value)

在进行工作循环之前,我希望了解这里发生了什么。然后我将添加一个解释。密钥的哪一部分是重复的?前缀还是数字部分?你搞定了。谢谢你,乔希!!欢迎别忘了修复你的insert语句,它有一天也会咬到你的。我从来没有见过输出被删除的事情。。。非常有趣。感谢您还可以使用OUTPUT INSERTED.next_值,该值将获得您插入的值,或者在本例中是您更新的值。你可以把它们混在一起。谢谢,但这并没有解决问题
update
    key_generation_table WITH (TABLOCK)
set
    @next_value = next_value, next_value = next_value + 1 
where
    prefix = @prefix

if @@ROWCOUNT = 0
begin
    set @next_value  = 1
    insert into key_generation_table (prefix, next_value) values (@prefix, 1)
end
select @string_next_value = convert(nvarchar(32),@next_value)