2

私は一見したところで、非常に単純な問題を抱えています。私はプレフィックスを持つ一意のキー値を取得できるようにしたい。私は 'Prefix'と 'Next_Value'列を含むテーブルを持っています。SQL Server 2005でトランザクションの整合性を保証する方法

あなたはトランザクションを開始し、このテーブルから次の値を取得し、テーブルの次の値をインクリメントしてコミットし、プレフィックスを値に連結して、一連の一意の英数字キー。

しかし、負荷がかかり、さまざまなサーバーが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] 
+0

どの部分が重複していますか?接頭辞または数字部分? – gbn

答えて

1

ifブロックの競合状態がいくつかあります。 2つの要求が同時に新しい接頭辞に入ると、両方ともifブロックを渡すことができます。これを変更して常にテーブルに挿入する必要がありますが、insertのwhere句には存在しないことを確認するためのチェックが行われます。また、count(*)= 0の代わりにExistsを使用することをお勧めします。 Existsを使用すると、sqlは行を見つけたら検索を停止できます。

これと同じことが起こる可能性があります.2つのスレッドが同じ値を選択すると、更新を待ってブロックされますが、それが戻ってきたら古い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   

は、私はまた、出力に含ま文を使用しての代わりに、第2の選択をして見たい得ます。

EDIT

私はprobally SQL2005に夜のため、出力を使用するようにこれを変更したい:

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 [email protected] 

/* Update the following to use your formating */ 
select next_value from @keyTable 
+0

あなたはそれを釘付けにしました。ありがとうジョシュ!! – rc1

+0

ようこそあなたもいつかあなたを噛んでしまうあなたの挿入ステートメントを修正することを忘れないでください – JoshBerke

+0

私は決してOUTPUT DELETED事を見たことがないでしょう...非常に興味深い。ありがとう – rc1

0

は、あなただけのAUTO_INCREMENT IDでテーブルに行を追加し、IDを使用することができますか?これは、負荷がかかっているとユニークであることが保証されますその後、行を削除することができます(テーブルが無限に成長しないように)。

何が起こっているかについてのご質問には、transactions aren't critical regions

SERIALIZABLE

ほとんど制限の分離レベル。使用すると、ファントム値は発生しません。トランザクションが完了するまで、他のユーザーが行をデータ・セットに更新または挿入するのを防止します。

このメカニズムが防止するように設計されている問題は、発生している問題とは異なります。

上記で概説したアプローチを使用する場合は、クリティカル領域の排他ロックを取得する必要があります。

+0

私はここで何が起こっているのか理解したいと思います。 – rc1

+0

それから説明を追加します。 – MarkusQ

+0

ありがとう - ありがとう! – rc1

1

UPDLOCKでヒンティング試してみてください。

 select @next_value = next_value 
     from key_generation_table WITH(UPDLOCK) 
     where prefix = @prefix 

key_generation_tableは、この特定のストアドプロシージャでのみ理想的に使用されます。さもなければ、UPDLOCKはデッドロックの確率を増加させる可能性があります。

+0

ありがとうが、これで問題は解決されませんでした – rc1

0

Serializableはロックを保持しますが、読み取りは許可されます。したがって、procが非常に速く並行して呼び出された場合、ロード中の選択/更新によって同じ結果が得られる可能性があります。 だと思います...

有効な構文を使用すると、2を組み合わせることができます。 タブロックは、テーブル全体が確実にロックされるようにします。これは並行処理であるシリアライザブルとは異なります。タブロックは粒度です。 また、必要に応じて欠落している接頭辞を追加します。

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) 
関連する問題