18

次のような単純なストアド プロシージャがあるとします (注: これは単なる例であり、実用的な手順ではありません)。

CREATE PROCEDURE incrementCounter AS

DECLARE @current int
SET @current = (select CounterColumn from MyTable) + 1

UPDATE
    MyTable
SET
    CounterColumn = current
GO

1 つの行を含む「myTable」というテーブルがあり、「CounterColumn」には現在のカウントが含まれていると想定しています。

このストアド プロシージャを同時に複数回実行できますか?

つまり、これは可能ですか:

「incrementCounter」を 2 回呼び出します。呼び出し A は、「現在の」変数 (5 としましょう) を設定するポイントに到達します。呼び出し B は、「現在の」変数 (これも 5 になります) を設定するポイントに到達します。コール A の実行が終了すると、コール B が終了します。最終的に、テーブルには 6 の値が含まれているはずですが、実行のオーバーラップにより代わりに 5 が含まれています。

4

5 に答える 5

13

これは SQL Server 用です。

各ステートメントはアトミックですが、ストアド プロシージャをアトミック (または一般的な一連のステートメント) にする場合は、ステートメントを次のように明示的に囲む必要があります。

BEGIN TRANSACTION
ステートメント ...
ステートメント ...
COMMIT TRANSACTION

(略して BEGIN TRAN と END TRAN を使用するのが一般的です。)

もちろん、同時に何が起こっているかによって、ロックの問題に陥る方法はたくさんあるので、失敗したトランザクションに対処するための戦略が必要になるかもしれません。(この特定の SP をどのように考案したとしても、ロックが発生する可能性のあるすべての状況の完全な議論は、質問の範囲を超えています。) そして、私の経験では、トランザクション量やデータベース上のその他のアクティビティについて知らなくても、おそらく大丈夫でしょう。当たり前のことを言ってすみません。

一般的な誤解に反して、これはデフォルトのトランザクションレベル設定で機能します。

于 2008-11-03T20:27:07.843 に答える
12

コードを aBEGIN TRANSACTIONとの間に配置することに加えてEND TRANSACTION、トランザクション分離レベルが正しく設定されていることを確認する必要があります。

たとえば、SERIALIZABLE分離レベルは、コードが同時に実行されたときに更新が失われるのを防ぎますが、READ COMMITTED(SQL Server Management Studio の既定) はできません。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

他の人がすでに述べたように、これは一貫性を確保しながら、ブロックやデッドロックを引き起こす可能性があるため、実際には最善の解決策ではない可能性があります.

于 2008-11-03T20:58:19.090 に答える
1

私はこの方法を使用します

CREATE PROCEDURE incrementCounter
AS

DECLARE @current int

UPDATE MyTable
SET
  @current = CounterColumn = CounterColumn + 1

Return @current

この手順では、2 つのコマンドすべてを一度に実行し、他のトランザクションから分離します。

于 2013-09-15T04:59:00.233 に答える
0

たぶん、私はあなたの例を読みすぎているかもしれません (実際の状況はもっと複雑かもしれません)。

CREATE PROCEDURE incrementCounter AS

UPDATE
    MyTable
SET
    CounterColumn = CounterColumn + 1

GO

そうすれば、自動的にアトミックになり、2 つの更新が同時に実行された場合、説明した競合を回避するために、それらは常に SQL Server によって順序付けられます。ただし、実際の状況がはるかに複雑な場合は、トランザクションでラップするのが最善の方法です。

ただし、別のプロセスが「安全性の低い」分離レベル (ダーティ リードまたは反復不可能な読み取りを許可するレベルなど) を有効にしている場合、別のプロセスが部分的に更新されたデータを参照できるため、トランザクションがこれに対して保護するとは思いません。安全でない読み取りを許可するように選択されている場合は、データ。

于 2010-01-19T03:52:15.337 に答える
0

あなたの質問に対する簡単な答えは「はい」です。ストアド プロシージャの同時実行をブロックする場合は、トランザクションを開始し、ストアド プロシージャを実行するたびに同じデータを更新してから、プロシージャ内の作業を続行します。

CREATE PROCEDURE ..
BEGIN TRANSACTION
UPDATE mylock SET ref = ref + 1
...

これにより、他のトランザクションが完了し、関連する更新ロックが解除されるまで「ref」値を変更できないため、他の同時実行は順番を待つことになります。

一般に、すべての SELECT クエリの結果は、実行されるに古くなると想定することをお勧めします。この不幸な現実を回避するために「重い」分離レベルを使用すると、スケーラビリティが大幅に制限されます。更新中に存在すると予想されるシステムの状態について楽観的な仮定を立てる方法で変更を構造化する方がはるかに優れているため、仮定が失敗した場合は後で再試行して、より良い結果を期待できます。例えば:

UPDATE
    MyTable
SET
    CounterColumn = current 
WHERE CounterColumn = current - 1

WHERE 句を追加した例を使用すると、現在の状態に関する仮定が失敗した場合、この更新は行に影響しません。@@ROWCOUNT をチェックして、行数をテストし、期待される結果とは異なる場合はロールバックまたはその他の適切なアクションを実行します。

于 2015-02-08T10:02:24.453 に答える