Come posso limitare una procedura memorizzata SQL che deve essere eseguita da una persona alla volta?


12

Ho una procedura memorizzata che sostanzialmente seleziona i valori da una tabella e li inserisce in un'altra, una sorta di archiviazione. Voglio evitare che più persone lo facciano contemporaneamente.

Mentre questa procedura è in esecuzione, non voglio che nessun altro sia in grado di avviarla, tuttavia non desidero la serializzazione, l'altra persona eseguirà la procedura dopo che ho terminato.

Quello che voglio è che l'altra persona cerchi di avviarlo per ottenere un errore, mentre sto eseguendo la procedura.

Ho provato ad usare sp_getapplock, tuttavia non riesco a fermare completamente la persona dall'esecuzione della procedura.

Ho anche provato a trovare la procedura con sys.dm_exec_requests e bloccando la procedura, mentre questo funziona, penso che non sia ottimale perché su alcuni server non ho i permessi per eseguire sys.dm_exec_sql_text (sql_handle).

Qual è il modo migliore per farlo?


3
Puoi fare un passo indietro e fornire ulteriori informazioni su cosa sta facendo la procedura e perché vuoi evitare che più persone lo eseguano contemporaneamente? Potrebbe esserci una tecnica di codifica che elimina questo requisito o una sorta di accodamento che potresti implementare per gestire le cose.
Due del

Risposte:


15

Per aggiungere alla risposta di @ Tibor-Karaszi, l'impostazione di un timeout di blocco in realtà non produce un errore (ho inviato un PR contro i documenti). sp_getapplock restituisce solo -1, quindi devi controllare il valore di ritorno. Quindi in questo modo:

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

Utilizzare sp_getapplock all'inizio del proc e impostare un timeout di blocco su un valore molto basso. In questo modo si ottiene un errore quando si è bloccati.


7

Un'altra opzione è quella di creare una tabella per controllare l'accesso alla procedura. l'esempio seguente mostra una possibile tabella e una procedura che potrebbe usarla.

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
Questo è molto simile a (o forse lo stesso) a quello che ho pensato subito dopo aver letto la domanda, ma avevo un problema con quell'idea che non ero del tutto sicuro di come affrontare e non riesco a vederlo affrontato nella tua risposta o. La mia preoccupazione è: cosa succede se succede qualcosa durante la parte "fai qualunque cosa debba essere fatta"? Come reimpostare lo IsLockedstato su 0 in quel caso? Sono anche curioso del tuo uso di COALESCEqui. Può @@ROWCOUNTessere nullo dopo dichiarazioni come UPDATE? Infine, solo un piccolo pignolo, perché mettere un punto e virgola davanti alla THROWdichiarazione in quel caso specifico?
Andriy M,

La scadenza del blocco è un modo per gestirlo. Dovrebbe essere impostato su un lasso di tempo ragionevole, nel mio esempio l'ho impostato su 10 minuti. Potresti incapsulare la tua logica di lavoro in un blocco try / catch e sbloccarlo anche se lo desideri. Uso COALESCE per abitudine, ma nessun @@ ROWCOUNT non può essere NULL. il punto e virgola principale viene dal lavorare con progetti di database di Visual Studio, si lamenta se non è presente. nessun danno in entrambi i modi.
Jonathan Fite,

-1

Penso che tu stia cercando di risolvere il problema in modo errato. Quello che vuoi è la massima protezione della coerenza del database. Se due persone eseguono una procedura memorizzata contemporaneamente, la coerenza del database potrebbe essere violata.

Per proteggere contro vari tipi di incoerenze del database, lo standard SQL ha quattro livelli di isolamento delle transazioni:

  • LEGGI SENZA COMPROMESSI dove sostanzialmente le transazioni perdono il loro valore, altre transazioni vedono dati sporchi. Non usare questo!
  • LEGGI IMPEGNATO in cui le transazioni visualizzano solo i dati impegnati, ma potrebbero esserci delle incoerenze in cui due transazioni possono scavalcare le dita dell'altro
  • RIPETIBILE LEGGI dove viene risolto un tipo di incoerenza, lettura non ripetibile
  • SERIALIZZABILE che garantisce l'esistenza di un ordine virtuale in cui l'esecuzione delle transazioni porterebbe ai risultati che ne derivavano

Tuttavia, lo standard SQL ha un approccio basato sul blocco per queste incoerenze del database e, per motivi di prestazioni, molti database adottano un approccio basato sull'isolamento dello snapshot che sostanzialmente ha questi livelli:

  • LEGGI IMPEGNATO che è lo stesso nei database basati sul blocco
  • ISOLAMENTO SNAPSHOT in cui il database vede un'istantanea di tutti i dati e se tenta di aggiornare una riga che è stata aggiornata da un'altra transazione, viene annullata, ma ci sono alcune anomalie ben note che possono verificarsi
  • SERIALIZZABILE, che è lo stesso dei database basati sul blocco, ma questa volta implementato in modo diverso, non prendendo i blocchi ma assicurando che non vi siano violazioni della serializzazione e, se viene rilevata una tale violazione, annullando una transazione

Le cancellazioni delle transazioni in questi database basati sull'isolamento dello snapshot possono sembrare preoccupanti, ma poi ogni singolo database annullerà una transazione a causa di un deadlock, quindi qualsiasi applicazione ragionevole deve comunque provare di nuovo una transazione.

Quello che vuoi è il livello di isolamento SERIALIZZABILE : assicura che se le transazioni eseguite in modo indipendente una dopo l'altra risultano in un buono stato, anche qualsiasi esecuzione parallela delle transazioni si traduce in un buono stato. Fortunatamente, nella sua tesi di dottorato , Michael Cahill ha scoperto come il livello di isolamento SERIALIZZABILE possa essere supportato da database di snapshot isolati con poco sforzo.

Se si utilizza un livello di isolamento SERIALIZZABILE in un database isolato di istantanee, se due persone provano a eseguire la procedura memorizzata contemporaneamente e si pestano reciprocamente, una delle transazioni verrebbe annullata.

Ora, SQL Server supporta realmente il livello di isolamento SERIALIZABLE (invece di mascherare l'isolamento dello snapshot dietro la parola chiave SERIALIZABLE )? Francamente, non lo so: l'unico database che conosco che lo supporta è PostgreSQL.

Anche se non sono riuscito a fornire consigli specifici su SQL Server, sto comunque pubblicando questa risposta, poiché gli utenti di PostgreSQL e gli utenti di altri database che possono prendere in considerazione il passaggio a PostgreSQL possono trarre vantaggio dalla mia risposta. Inoltre, gli utenti di database non PostgreSQL che non possono passare a PostgreSQL possono fare pressione sul proprio fornitore di database preferito per offrire un livello di isolamento SERIALIZZABILE autentico .


Immagino che il downvote significhi che qualcuno ha indagato se SQL Server ha il livello di isolamento SERIALIZZABILE e ha scoperto che non lo è.
juhist,

-2

Mi rendo conto che il problema "reale" potrebbe essere più complesso.

In caso contrario: se esegui l'archiviazione con trigger di inserimento e / o aggiornamento, puoi evitare il problema che stai cercando di risolvere.

Spero che aiuti,
-Chris C.


1
Cosa intendi con "immediatamente"? Subito dopo cosa? Dopo l'inserimento? Quindi appena arriva una nuova riga, viene immediatamente inviata all'archivio? O intendevi dopo l'aggiornamento? Quindi qualsiasi modifica dei dati innesca l'archiviazione? Forse dovresti essere più specifico su quale scenario hai in mente di suggerire questo.
Andriy M,

L'archiviazione potrebbe essere troppo costosa e / o troppo raramente desiderata per essere valsa la pena fare su ogni inserto, specialmente se la tabella di origine viene frequentemente inserita e / o la sicurezza transazionale tra essa e l'archivio richiederebbe costosi blocchi.
underscore_d

@underscore_d Sì, potrebbe essere troppo costoso o non sempre richiesto. Questo è il motivo per cui ho iniziato la mia risposta affermando che the 'real' problem may be more complex. Nel caso in cui non lo sia, i trigger sono una buona soluzione. Inoltre, sarà probabilmente più facile testarlo e gestirlo perché è una funzionalità del database anziché una soluzione personalizzata.
J. Chris Compton,

@AndriyM Ho rimosso immediatamente la parola, sostituendola con un riferimento per inserire / aggiornare i trigger. Dispiace per la confusione.
J. Chris Compton,

1
Ho riletto la domanda e penso di poter vedere l'origine della mia confusione. Quello che stai suggerendo qui è più simile all'auditing che all'archiviazione. A quanto ho capito, l'archiviazione dei dati implica lo spostamento dei dati (ad es. Da una tabella all'altra). Tuttavia, anche se il PO ha sintetizzato la funzione della loro procedura come "una sorta di archiviazione", non hanno mai detto che i dati sarebbero stati rimossi dalla fonte, solo che sarebbero stati selezionati da esso e inseriti nella destinazione. Quindi immagino che tu abbia supposto che l'OP abbia bisogno di copiare , piuttosto che spostare , i loro dati, nel qual caso probabilmente usare i trigger ha senso.
Andriy M,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.