web-dev-qa-db-ja.com

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

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

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

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

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

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

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

12
twoheadedmona

@ 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

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

8
Tibor Karaszi

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

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
7
Jonathan Fite