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