SQLストアドプロシージャを一度に1人で実行するように制限するにはどうすればよいですか?


12

基本的に、あるテーブルから値を選択して別のテーブルに挿入するストアドプロシージャがあります。これは一種のアーカイブです。複数の人が同時にそうするのを避けたいです。

このプロシージャが実行されている間、他の人がそれを開始できるようにしたくはありませんが、シリアライゼーションは必要ありません。他の人は、処理が終わった後にプロシージャを実行します。

私が欲しいのは、プロシージャを実行しているときに、他の人がそれを開始しようとしてエラーが発生することです。

私はsp_getapplockを使用して試しましたが、その人がプロシージャを実行するのを完全に止めることができません。

また、sys.dm_exec_requestsを使用してプロシージャを見つけてブロックしましたが、これは機能しますが、一部のサーバーではsys.dm_exec_sql_text(sql_handle)を実行する権限がないため、最適ではないと思います。

これを行うための最良の方法は何ですか?


3
1つ前の手順に戻って、手順の実行内容、および複数の人が同時にそれを実行することを避けたい理由についての情報を提供できますか?この要件を排除するコーディング手法、または物事を処理するために実装できるある種のキューイングがあるかもしれません。
AMtwo 2018年

回答:


15

@ Tibor-Karasziの回答に追加するために、ロックタイムアウトを設定しても実際にはエラーは発生しません(ドキュメントに対するPRを送信しました)。sp_getapplockは-1を返すだけなので、戻り値を確認する必要があります。このように:

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end

8

プロシージャの最初でsp_getapplockを使用し、ロックタイムアウトを非常に低い値に設定します。これにより、ブロックされているときにエラーが発生します。


7

別のオプションは、プロシージャへのアクセスを制御するテーブルを作成することです。以下の例は、可能なテーブルとそれを使用できるプロシージャを示しています。

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END

1
これは質問を読んだ直後に私が考えたものと非常によく似ています(または同じかもしれません)が、その対処方法が完全にわからず、回答でそれを確認できないという問題がありました。どちらか。私の懸念は、「何をする必要があるか」の部分で何かが起こった場合はどうなりますか?IsLockedその場合、どのように状態を0にリセットしますか?COALESCEここでの使用についても知りたいです。次の@@ROWCOUNTようなステートメントの後でnullにすることができますUPDATE。最後に、ちょっとしたちょっとしたコツですが、なぜTHROWその特定のケースでステートメントの前にセミコロンを付けるのですか?
Andriy M

ロックの有効期限は、それを処理する1つの方法です。適切な時間枠に設定する必要があります。この例では10分に設定しています。必要に応じて、try / catchブロックに作業ロジックをカプセル化し、catchでロックを解除することもできます。習慣からCOALESCEを使用していますが、@@ ROWCOUNTをNULLにすることはできません。先頭のセミコロンはVisual Studioデータベースプロジェクトでの作業に由来し、そこにない場合は文句を言います。どちらの方法でも害はありません。
Jonathan Fite、2018年

-1

あなたは問題を間違った方法で解決しようとしていると思います。必要なのは、データベースの一貫性を最大限に保護することです。2人が同時にストアドプロシージャを実行すると、データベースの一貫性が損なわれる可能性があります。

さまざまな種類のデータベースの不整合から保護するために、SQL標準には4つのトランザクション分離レベルがあります。

  • READ UNCOMMITTEDでは、基本的にトランザクションの価値が失われ、他のトランザクションではダーティデータが表示されます。これは使わないでください!
  • READ COMMITTEDの場合、トランザクションはコミットされたデータのみを認識しますが、2つのトランザクションが互いのつま先をまたぐことができる不整合がある場合があります
  • 1種類の不整合、反復不能な読み取りが解決されるREPEATABLE READ
  • SERIALIZABLEは、トランザクションを実行すると、トランザクションの実行によって結果がもたらされる仮想順序が存在することを保証します。

ただし、SQL標準には、これらのデータベースの不整合に対するロックベースのアプローチがあり、パフォーマンス上の理由から、多くのデータベースは基本的に次のレベルを持つスナップショット分離ベースのアプローチを採用しています。

  • READ COMMITTEDは、ベースのデータベースのロックと同じです。
  • SNAPSHOT ISOLATIONは、データベースがすべてのデータのスナップショットを確認し、他のトランザクションによって更新された行を更新しようとするとキャンセルされますが、発生する可能性のある既知の異常があります。
  • ベースのデータベースをロックする場合と同じSERIALIZABLEですが、今回はロックを取得するのではなく、シリアル化違反がないことを確認することにより、別の方法で実装し、そのような違反が検出された場合はトランザクションをキャンセルします

これらのスナップショット分離ベースのデータベースでのトランザクションのキャンセルは心配に聞こえるかもしれませんが、デッドロックが原因ですべてのデータベースがトランザクションをキャンセルするため、いずれにせよトランザクションはトランザクションを再試行できるようにする必要があります。

必要なのはSERIALIZABLE分離レベルです。これは、トランザクションが次々と独立して実行されて良好な状態になる場合、トランザクションの並列実行でも良好な状態になることを保証します。幸いなことに、マイケル・ケイヒルは博士論文で、SERIALIZABLE分離レベルがスナップショット分離データベースによってわずかな労力でサポートされる方法を発見しました。

スナップショット分離データベースでSERIALIZABLE分離レベルを使用している場合、2人が同時にストアドプロシージャを実行しようとして、お互いのつま先を踏むと、トランザクションの1つがキャンセルされます。

SQL ServerはSERIALIZABLE分離レベルを純粋にサポートしていますか(SERIALIZABLEキーワードの背後でスナップショット分離をマスカレードする代わりに)?正直なところ、わかりません。それをサポートしているデータベースはPostgreSQLだけです。

SQL Server固有のアドバイスを提供できませんでしたが、PostgreSQLのユーザーやPostgreSQLへの切り替えを検討できる他のデータベースのユーザーが私の回答から利益を得ることができるため、それでもこの回答を投稿しています。また、PostgreSQLに切り替えることができない非PostgreSQLデータベースのユーザーは、お気に入りのデータベースベンダーに本物のシリアライズ可能分離レベルを提供するよう圧力をかけることができます。


私はそれを反対票とみなします。誰かがSQL ServerにSERIALIZABLE分離レベルがあるかどうかを調査したところ、そうではないことがわかりました。
juhist

-2

「本当の」問題はもっと複雑かもしれません。

そうでない場合:挿入や更新のトリガーを使用してアーカイブを行うと、解決しようとしている問題を回避できます。

お役に立てれば
幸いです-Chris C.


1
「すぐに」とはどういう意味ですか?すぐ後は?挿入後?新しい行が到着するとすぐに、それはすぐにアーカイブに送信されますか?それとも更新後ですか?では、データの変更はアーカイブをトリガーするのでしょうか?おそらく、これを示唆しているシナリオについて、より具体的にする必要があります。
Andriy M

特にソーステーブルが頻繁に挿入される場合やアーカイブとの間のトランザクションの安全性のために高価なロックが必要になる場合、アーカイブはコストがかかりすぎるか、または挿入ごとに実行する価値があまりにも低い場合があります。
underscore_d

@underscore_dはい、コストがかかりすぎるか、必ずしも必要とは限りません。これが、私がその発言から私の答えを始めた理由ですthe 'real' problem may be more complex。そうでない場合、トリガーは良い解決策です。さらに、カスタムソリューションではなくデータベースの機能であるため、おそらくテストと保守が容易になります。
J.クリスコンプトン

@AndriyM単語をすぐに削除し、トリガーを挿入/更新するための参照に置き換えました。混乱させて申し訳ありません。
J.クリスコンプトン

1
質問をもう一度読んだので、混乱の原因がわかります。ここで提案しているのは、アーカイブというより監査に近いものです。私が理解しているように、データのアーカイブは、データを移動することを意味します(たとえば、あるテーブルから別のテーブルへ)。ただし、OPはプロシージャの機能を「一種のアーカイブ」として要約しましたが、データがソースから削除され、そこから選択されてターゲットに挿入されるだけであるとは言いませんでした。OPがデータを移動するのではなくコピーする必要があると想定していると思います。その場合、トリガーを使用することはおそらく意味があります。
Andriy M
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.