web-dev-qa-db-fra.com

Comment limiter une procédure stockée SQL à une seule personne à la fois?

J'ai une procédure stockée qui sélectionne essentiellement les valeurs d'une table et les insère dans une autre, une sorte d'archivage. Je veux éviter que plusieurs personnes le fassent en même temps.

Pendant que cette procédure est en cours d'exécution, je ne veux pas que quelqu'un d'autre puisse la démarrer, mais je ne veux pas de sérialisation, l'autre personne exécutera la procédure une fois que j'en aurai fini.

Ce que je veux, c'est que l'autre personne essayant de le démarrer obtienne une erreur pendant que j'exécute la procédure.

J'ai essayé d'utiliser sp_getapplock, mais je n'arrive pas à empêcher complètement la personne d'exécuter la procédure.

J'ai également essayé de trouver la procédure avec sys.dm_exec_requests et de bloquer la procédure, alors que cela fonctionne, je pense que ce n'est pas optimal car sur certains serveurs, je n'ai pas les autorisations pour exécuter sys.dm_exec_sql_text (sql_handle).

Quelle est la meilleure façon pour moi de le faire?

12
twoheadedmona

Pour ajouter à la réponse de @ Tibor-Karaszi, la définition d'un délai de verrouillage ne produit pas réellement d'erreur (j'ai soumis un PR contre les documents). sp_getapplock renvoie simplement -1, vous devez donc vérifier la valeur de retour. Alors comme ça:

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

Utilisez sp_getapplock au début du processus et définissez un délai de verrouillage sur une valeur très faible. De cette façon, vous obtenez une erreur lorsque vous êtes bloqué.

8
Tibor Karaszi

Une autre option consiste à créer une table pour contrôler l'accès à la procédure. l'exemple ci-dessous montre un tableau possible ainsi qu'une procédure qui pourrait l'utiliser.

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