基本的に、1つのテーブルから値を選択して別のテーブルに挿入するストアドプロシージャがあります。これは一種のアーカイブです。複数の人が同時にそうするのを避けたいです。
このプロシージャが実行されている間、他の人がそれを開始できるようにしたくはありませんが、シリアライゼーションは必要ありません。他の人は、処理が終わった後にプロシージャを実行します。
私が欲しいのは、プロシージャを実行しているときに、他の人がそれを開始しようとしてエラーが発生することです。
私はsp_getapplockを使用して試しましたが、その人がプロシージャを実行するのを完全に止めることができません。
また、sys.dm_exec_requestsを使用してプロシージャを見つけてブロックしましたが、これは機能しますが、一部のサーバーではsys.dm_exec_sql_text(sql_handle)を実行する権限がないため、最適ではないと思います。
これを行うための最良の方法は何ですか?
@ 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を使用し、ロックタイムアウトを非常に低い値に設定します。これにより、ブロックされているときにエラーが発生します。
別のオプションは、プロシージャへのアクセスを制御するテーブルを作成することです。以下の例は、可能なテーブルとそれを使用できるプロシージャを示しています。
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