Comment puis-je limiter une procédure stockée SQL à exécuter par une seule personne à la fois?


12

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?


3
Pouvez-vous faire un pas en arrière et fournir plus d'informations sur ce que fait la procédure et pourquoi vous voulez éviter que plusieurs personnes l'exécutent en même temps? Il peut y avoir une technique de codage qui élimine cette exigence, ou une sorte de file d'attente que vous pourriez implémenter pour gérer les choses.
AMtwo

Réponses:


15

Pour ajouter à la réponse de @ Tibor-Karaszi, la définition d'un délai d'expiration 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

8

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é.


7

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

1
Ceci est très similaire (ou peut-être le même que) à ce que j'ai immédiatement pensé après avoir lu la question, mais j'ai eu un problème avec cette idée que je ne savais pas trop comment répondre et que je ne peux pas le voir abordé dans votre réponse Soit. Ma préoccupation est la suivante: que se passe-t-il si quelque chose se produit pendant la partie «faire tout ce qui doit être fait»? Comment voulez-vous réinitialiser l' IsLockedétat à 0 dans ce cas? Je suis également curieux de savoir si vous l'utilisez COALESCEici. Peut @@ROWCOUNTêtre nul après des instructions comme UPDATE? Enfin, juste un petit coup de pouce, pourquoi mettre un point-virgule devant la THROWdéclaration dans ce cas spécifique?
Andriy M

L'expiration du verrou est une façon de gérer cela. Il faudrait qu'il soit défini sur un délai raisonnable, je l'ai défini sur 10 minutes dans mon exemple. Vous pouvez également encapsuler votre logique de travail dans un bloc try / catch et déverrouiller dans le catch si vous le souhaitez. J'utilise COALESCE par habitude, mais aucun @@ ROWCOUNT ne peut pas être NULL. le point-virgule principal provient du travail avec les projets de base de données Visual Studio, il se plaint s'il n'est pas là. aucun mal de toute façon.
Jonathan Fite

-1

Je pense que vous essayez de résoudre le problème d'une manière incorrecte. Ce que vous voulez, c'est une protection maximale de la cohérence de la base de données. Si deux personnes exécutent une procédure stockée en même temps, la cohérence de la base de données peut être violée.

Pour se protéger contre divers types d'incohérences de base de données, la norme SQL a quatre niveaux d'isolement des transactions:

  • LIRE NON ENGAGÉ où les transactions perdent essentiellement de leur valeur, d'autres transactions voient des données sales. Ne l'utilisez pas!
  • LIRE COMMIS lorsque les transactions ne voient que les données validées, mais il peut y avoir des incohérences lorsque deux transactions peuvent se chevaucher mutuellement
  • REPEATABLE READ où un type d'incohérence, lecture non répétable, est résolu
  • SERIALIZABLE qui garantit qu'il existe un certain ordre virtuel dans lequel l'exécution des transactions conduirait aux résultats que leur exécution a entraîné

Cependant, la norme SQL a une approche basée sur le verrouillage pour ces incohérences de base de données, et pour des raisons de performances, de nombreuses bases de données adoptent une approche basée sur l'isolement d'instantané qui a essentiellement ces niveaux:

  • LIRE ENGAGÉ qui est le même que c'est dans le verrouillage des bases de données
  • SNAPSHOT ISOLATION où la base de données voit un instantané de toutes les données et si elle essaie de mettre à jour une ligne qui a été mise à jour par une autre transaction, elle est annulée, mais il existe des anomalies bien connues qui peuvent avoir lieu
  • SERIALIZABLE qui est le même que dans les bases de données de verrouillage, mais cette fois implémenté d'une manière différente, non pas en prenant des verrous mais en s'assurant qu'il n'y a pas de violation de sérialisation, et si une telle violation est détectée, l'annulation d'une transaction

Les annulations de transactions dans ces bases de données basées sur l'isolement d'instantané peuvent sembler inquiétantes, mais là encore, chaque base de données annulera une transaction en raison d'un blocage, de sorte que toute application raisonnable doit de toute façon être en mesure de réessayer une transaction.

Ce que vous voulez, c'est le niveau d'isolement SERIALISABLE : il garantit que si les transactions exécutées indépendamment les unes après les autres aboutissent à un bon état, toute exécution parallèle des transactions se traduit également par un bon état. Heureusement, Michael Cahill a découvert dans sa thèse de doctorat comment le niveau d'isolement SERIALISABLE peut être supporté par des bases de données instantanées isolées avec peu d'effort.

Si vous utilisez un niveau d'isolement SERIALIZABLE dans une base de données isolée par instantané, si deux personnes tentent d'exécuter la procédure stockée simultanément et qu'elles se mettent sur les pieds l'une de l'autre, l'une des transactions est annulée.

Désormais, SQL Server prend-il réellement en charge le niveau d'isolement SERIALIZABLE (au lieu de masquer l'isolement d'instantané derrière le mot clé SERIALIZABLE )? Franchement, je ne sais pas: la seule base de données que je connaisse qui le supporte est PostgreSQL.

Même si je n'ai pas donné de conseils spécifiques à SQL Server, je poste quand même cette réponse, car les utilisateurs de PostgreSQL et les utilisateurs d'autres bases de données qui peuvent envisager de passer à PostgreSQL peuvent bénéficier de ma réponse. De plus, les utilisateurs de bases de données non PostgreSQL qui ne peuvent pas passer à PostgreSQL peuvent faire pression sur leur fournisseur de base de données préféré pour offrir un véritable niveau d'isolement SERIALISABLE .


Je suppose que le downvote signifie que quelqu'un a recherché si SQL Server a le niveau d'isolement SERIALIZABLE et a découvert que ce n'était pas le cas.
juhist

-2

Je me rends compte que le «vrai» problème peut être plus complexe.

Dans le cas contraire: si vous effectuez votre archivage avec des déclencheurs d'insertion et / ou de mise à jour, vous pouvez éviter le problème que vous essayez de résoudre.

J'espère que ça aide,
-Chris C.


1
Qu'entendez-vous par «immédiatement»? Immédiatement après quoi? Après l'insertion? Donc, dès qu'une nouvelle ligne arrive, elle est immédiatement envoyée aux archives? Ou vouliez-vous dire après la mise à jour? Donc, tout changement de données déclenche l'archivage? Peut-être devriez-vous être plus précis sur le scénario auquel vous pensez proposer cela.
Andriy M

L'archivage peut être trop coûteux et / ou trop rarement souhaité pour être utile à chaque insertion, en particulier si la table source est fréquemment insérée et / ou la sécurité transactionnelle entre elle et l'archive nécessiterait des verrous coûteux.
underscore_d

@underscore_d Oui, cela peut être trop coûteux ou pas toujours nécessaire. C'est pourquoi j'ai commencé ma réponse par la déclaration suivante the 'real' problem may be more complex. Dans le cas contraire, les déclencheurs sont une bonne solution. De plus, il sera probablement plus facile à tester et à maintenir car il s'agit d'une fonctionnalité de la base de données au lieu d'une solution personnalisée.
J. Chris Compton

@AndriyM J'ai supprimé le mot immédiatement, en le remplaçant par une référence pour insérer / mettre à jour les déclencheurs. Désolé pour la confusion.
J. Chris Compton

1
J'ai relu la question et je pense que je peux voir la source de ma confusion. Ce que vous proposez ici s'apparente davantage à l'audit qu'à l'archivage. Si je comprends bien, l'archivage des données implique le déplacement des données (par exemple d'une table à une autre). Cependant, même si l'OP résumait la fonction de leur procédure comme "une sorte d'archivage", ils n'ont jamais dit que les données seraient supprimées de la source, seulement qu'elles seraient sélectionnées et insérées dans la cible. Je suppose donc que vous supposiez que l'OP doit copier , plutôt que déplacer , leurs données, auquel cas l'utilisation de déclencheurs est probablement logique.
Andriy M
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.