1

カウンタの値をインクリメントして、その呼び出しがMaxValueに達した場合に返すストアドプロシージャを作成します。トリッキーな部分は、この手順は、別のスレッドや異なるマシンから迅速かつ並列に呼び出すことです。カウンタを増やして原子単位で確実にストアドプロシージャを呼び出す

シナリオ例:平行コールで同じストアドプロシージャを実行

2つのスレッドが同一のカウンタをインクリメントします。 CounterId = 5が両方のパラメータとして渡されると仮定します。いずれかのカウンタレコードを実行する前に、現在、フィールド値はCounterValue = 9MaxValue = 10です。

私は、CurrentValueを10に正常にインクリメントし、CounterValueがMaxValueに達する原因となった変更の原因であることを示す結果を返すプロシージャの1つを実行します。もう1つの手順では値を増やしてはいけません(10を超えるため)、MaxReachが既にカウンタに一致したことを示す結果を返す必要があります。

私は前後にクエリを実行することを考えましたが、別のスレッドで変更が行われ、偽陽性/陰性が返される可能性のある「穴」を残す可能性があります。

これは手順のアイデアの始まりに過ぎません。私はそれがロック、トランザクションまたは何かが必要なように感じる?

あなたが取引でそれをラップし、次のように、同じトランザクション内の選択を追加する必要が
 
UPDATE SomeCounters 
SET CounterValue = (CounterValue + @AddValue), 
    MaxReached = CASE WHEN MaxValue = (CurrentValue + 1) THEN 1 ELSE 0 
WHERE CounterId = @CounterId 
    AND MaxReached = 0 
+0

?何のために? –

+0

はい、本当の必要条件です。私はデータの流れを追跡しており、データが特定の基準に一致する回数を記録し、MaxValueが満たされたときに追加の作業をトリガする必要があります。現在のシステムでは、Azureテーブルストレージにデータとカウンタが格納され、オプティミスティック並行性を使用して、必要な最終増分を実行することで誰が勝利したかを管理します。パフォーマンスを向上させるために、カウンタをSQL Azureに移動したいと考えています。 – Vyrotek

答えて

3

使用OUTPUT

DECLARE @temp TABLE (MaxReached BIT NOT NULL); 

UPDATE SomeCounters 
    SET CounterValue = (CounterValue + @AddValue), 
     MaxReached = CASE WHEN MaxValue = (CurrentValue + 1) THEN 1 ELSE 0 
    WHERE CounterId = @CounterId 
    AND MaxReached = 0 
    OUTPUT INSERTED.MaxReached INTO @temp 

更新はアトミックであり、あなたはその後@tempテーブルのうち、値を選択して、あなたはそれでやりたいことができます。これにより、MaxReachedをtrue(1)に設定した正確な更新を取得することができます。

+0

私はこれを実装しましたが、うまくいくようです。 – Vyrotek

1

BEGIN TRANSACTION; 

UPDATE SomeCounters 
SET CounterValue = (CounterValue + @AddValue) 
WHERE CounterId = @CounterId; 

SELECT CASE WHEN MaxValue = CurrentValue THEN 1 ELSE 0 MaxReached 
FROM SomeCounters 
WHERE CounterId = @CounterId; 

COMMIT TRANSACTION; 

それはから返されるようにするには、出力パラメータにその最後の部分を置くことができますproc。 MaxValueは、よく知られており、各カウンタに対して同じであると仮定すると

+0

トランザクションで両方のステートメントをラップすると、Selectクエリが呼び出されたときに、前回の更新ステートメントによってのみ変更された行が返されます。つまり、Proc Aが選択を実行する前にProc Bで更新が発生することはありえませんか? – Vyrotek

+0

@Vyrotek - 実際には、孤立レベルを取得するには、トランザクション分離レベルをSerializableに設定する必要があります。 – Thomas

+0

必ずしもそうではありません。更新を行うと、行のWRITEロックが自動的に取り出されます。トランザクションをコミットするまで、他の誰も書き込むことはできず、READ_UNCOMMITTEDで実行されている他のトランザクションだけが、トランザクションがコミットされる前に更新された値を読み取ることができます。 – squawknull

-1

、あなたはトランザクションを必要としない:

UPDATE CounterTable 
SET Counter=Counter+1 
WHERE CounterId = @CounterId 

は、これはデータベースではなく、マルチスレッドプログラムです。これは、テーブルの1つの行のカウンタ列の値をインクリメントするためのSQL Serverへの要求です。 SQL Serverはそれを行います - 私はそれがテーブルが要求の1つを失うのを許可するとは思わない。

したがって、最悪の場合、Counter > MaxValueとなる場合があります。しかし、あなたがMaxValueが何であるか知っていれば、それ以上の値は実際にはMaxValueを意味することがわかります。同じトランザクションでの仕事を即時にスケジュールする必要はありません

「余分な仕事」がどのくらい時間がかかるかによって、MaxValue以上のカウンター値を探している仕事や他のプログラムのクエリを実行して、すぐに仕事をしてください。最悪の場合、UPDATEごとにトリガーを作成してください。カウンター値が高い場合にのみ作業が行われます。

カウンタの更新と同じトランザクションで「余分な作業」を実行する必要がない限り、トランザクションは必要ありません。今のところトランザクションを使用しているとは言わないので、同じトランザクションで「余分な作業」が発生する必要はないと思われます。

+0

私が話している余分な仕事は、実行したばかりの更新がMaxValueに達する原因となった場合にのみ、プロシージャの実行直後に実行するC#コードです。私はCurrentValueがMaxValueを超えて成長することを望んでいません。 SQL Azureでは使用できないため、ジョブを使用できません。 – Vyrotek

+0

これを含めるには、回答を更新してください。それは重要です。 –

0

あなたが探しているものを達成する1つの方法は、悲観的なアプローチを取ることです。つまり、各ストアドプロシージャは、別のストアドプロシージャによって変更されていないレコードのみを更新し、最大に達するまで再試行します。これを行うには、更新前に現在の値を読み取ってから、値が同じになることを期待するWHERE句でレコードを更新する必要があります。呼び出しが最終的に成功することを確認する必要がある場合は、ループも必要です。このアプローチを使用すると、ストアード・プロシージャーが1回だけテーブルを更新し、最大に達するまで作業を再試行します。このような

何か:

DECLARE @savedValue int 
DECLARE @maxedReached int 
-- read current values for concurrency 
SELECT @savedValue = CounterValue, @maxedReached = MaxReached 
    FROM SomeCounters WHERE CounterId = @counterId) 

WHILE(@maxedReached = 0) 
BEGIN 

    UPDATE SomeCounters 
    SET CounterValue = (CounterValue + @AddValue),  
    MaxReached = CASE WHEN MaxValue = (CurrentValue + 1) THEN 1 ELSE 0 END 
    WHERE 
    CounterId = @CounterId 
    AND MaxReached = 0 
    -- the next clause ensures that only one stored procedure will succeed 
    AND CounterValue = @savedValue 

    if (@@rowcount = 0) 
    BEGIN 
    -- failed... another procedure made the change? 
    -- If @maxReached becomes 1, the loop will exit and you will 
    -- know the maximum was reached; if not the loop will try updating 
    -- the value again 
    -- read the values for concurrency again. 
    SELECT @savedValue = CounterValue, @maxedReached = MaxReached 
     FROM SomeCounters WHERE CounterId = @counterId) 

    END 
END 
0

私が調査してる別の戦略は、トランザクション内sp_getapplockの使用です。これは、私が更新しようとしているカウンタのための一意の文字列を作成し、それが完了するまで他の同時実行をブロックすることができるようです。

私の手順には、IF EXISTS ... ELSEロジックが含まれているため、これは特に便利なようです。カウンタ・レコードの作成または更新と既存のものを処理するロジックです。

http://msdn.microsoft.com/en-us/library/ms189823.aspx - sp_getapplockあなたはトランザクションが必要になり、これは本当の要件のためである

関連する問題