Wie beschränke ich die Speicherung einer gespeicherten SQL-Prozedur auf jeweils eine Person?

12

Ich habe eine gespeicherte Prozedur, die im Grunde genommen Werte aus einer Tabelle auswählt und in eine andere einfügt, eine Art Archivierung. Ich möchte vermeiden, dass mehrere Personen dies gleichzeitig tun.

Während diese Prozedur ausgeführt wird, möchte ich nicht, dass jemand anderes sie starten kann. Ich möchte jedoch nicht, dass die andere Person die Prozedur ausführt, nachdem ich damit fertig bin.

Ich möchte, dass die andere Person, die versucht, es zu starten, einen Fehler erhält, während ich die Prozedur ausführe.

Ich habe versucht, sp_getapplock zu verwenden, kann es jedoch nicht schaffen, die Person vollständig daran zu hindern, die Prozedur auszuführen.

Ich habe auch versucht, die Prozedur mit sys.dm_exec_requests zu finden und die Prozedur zu blockieren, obwohl dies funktioniert, denke ich, dass es nicht optimal ist, da ich auf einigen Servern nicht die Berechtigung habe, sys.dm_exec_sql_text (sql_handle) auszuführen.

Was ist der beste Weg für mich, dies zu tun?

zweiköpfige Mona
quelle
3
Können Sie einen Schritt zurücktreten und weitere Informationen darüber geben, was das Verfahren tut und warum Sie vermeiden möchten, dass mehrere Personen es gleichzeitig ausführen? Möglicherweise gibt es eine Codierungstechnik, die diese Anforderung beseitigt, oder eine Art Warteschlange, die Sie implementieren können, um Dinge zu erledigen.
AMtwo

Antworten:

15

Um die Antwort von @ Tibor-Karaszi zu ergänzen, führt das Festlegen eines Sperrzeitlimits nicht zu einem Fehler (ich habe eine PR für die Dokumente eingereicht). sp_getapplock gibt nur -1 zurück, daher müssen Sie den Rückgabewert überprüfen. Also so:

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
David Browne - Microsoft
quelle
8

Verwenden Sie sp_getapplock am Anfang des Prozesses und setzen Sie ein Sperrzeitlimit auf einen sehr niedrigen Wert. Auf diese Weise erhalten Sie eine Fehlermeldung, wenn Sie blockiert sind.

Tibor Karaszi
quelle
7

Eine andere Möglichkeit besteht darin, eine Tabelle zu erstellen, um den Zugriff auf die Prozedur zu steuern. Das folgende Beispiel zeigt eine mögliche Tabelle sowie eine Prozedur, die sie verwenden könnte.

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
Jonathan Fite
quelle
1
Dies ist sehr ähnlich (oder vielleicht das gleiche wie), woran ich sofort nach dem Lesen der Frage gedacht habe, aber ich hatte ein Problem mit der Idee, dass ich nicht ganz sicher war, wie ich es ansprechen soll, und es in Ihrer Antwort nicht angesprochen sehen kann entweder. Mein Anliegen ist, was passiert, wenn während des Teils "Alles tun, was getan werden muss" etwas passiert? Wie würden Sie IsLockedin diesem Fall den Status auf 0 zurücksetzen ? Ich bin auch neugierig auf Ihre Verwendung COALESCEhier. Kann @@ROWCOUNTnach Aussagen wie null sein UPDATE? Schließlich, nur ein kleiner Trottel, warum THROWin diesem speziellen Fall ein Semikolon vor die Aussage setzen?
Andriy M
Der Ablauf der Sperre ist eine Möglichkeit, dies zu handhaben. Es müsste auf einen angemessenen Zeitrahmen eingestellt werden, ich habe es in meinem Beispiel auf 10 Minuten eingestellt. Sie können Ihre Arbeitslogik in einen Try / Catch-Block einkapseln und im Catch entsperren, wenn Sie dies möchten. Ich benutze COALESCE aus Gewohnheit, aber kein @@ ROWCOUNT kann nicht NULL sein. Das führende Semikolon stammt aus der Arbeit mit Visual Studio-Datenbankprojekten. Es beschwert sich, wenn es nicht vorhanden ist. kein Schaden so oder so.
Jonathan Fite
-1

Ich denke, Sie versuchen, das Problem auf falsche Weise zu lösen. Was Sie wollen, ist ein Höchstmaß an Schutz der Datenbankkonsistenz. Wenn zwei Personen gleichzeitig eine gespeicherte Prozedur ausführen, kann die Datenbankkonsistenz verletzt werden.

Zum Schutz vor verschiedenen Arten von Datenbankinkonsistenzen verfügt der SQL-Standard über vier Transaktionsisolationsstufen:

  • LESEN SIE UNCOMMITTED, wenn Transaktionen im Grunde ihren Wert verlieren und andere Transaktionen schmutzige Daten sehen. Benutze das nicht!
  • READ COMMITTED, wenn bei Transaktionen nur festgeschriebene Daten angezeigt werden, es jedoch zu Inkonsistenzen kommen kann, bei denen zwei Transaktionen einander über die Zehen treten können
  • REPEATABLE READ, bei dem eine Art von Inkonsistenz, nicht wiederholbares Lesen, gelöst wird
  • SERIALIZABLE, das garantiert, dass es eine virtuelle Reihenfolge gibt, in der die Ausführung der Transaktionen zu den Ergebnissen führen würde, zu denen ihre Ausführung geführt hat

Der SQL-Standard verwendet jedoch einen auf Sperren basierenden Ansatz für diese Datenbankinkonsistenzen. Aus Leistungsgründen verwenden viele Datenbanken einen auf Snapshot-Isolation basierenden Ansatz, der im Wesentlichen folgende Ebenen aufweist:

  • READ COMMITTED ist das gleiche wie beim Sperren von Datenbanken
  • SNAPSHOT ISOLATION, bei der die Datenbank einen Snapshot aller Daten sieht und versucht, eine Zeile zu aktualisieren, die durch eine andere Transaktion aktualisiert wurde, wird sie abgebrochen, es können jedoch einige bekannte Anomalien auftreten
  • SERIALIZABLE ist dasselbe wie in sperrbasierten Datenbanken, diesmal jedoch auf andere Weise implementiert, nicht durch Sperren, sondern durch Sicherstellen, dass keine Serialisierungsverletzungen vorliegen. Wenn eine solche Verletzung erkannt wird, wird eine Transaktion abgebrochen

Die Transaktionsstornierungen in diesen auf Snapshot-Isolation basierenden Datenbanken klingen möglicherweise besorgniserregend, aber andererseits bricht jede einzelne Datenbank eine Transaktion aufgrund eines Deadlocks ab, sodass jede vernünftige Anwendung ohnehin in der Lage sein muss, eine Transaktion erneut zu versuchen.

Was Sie wollen, ist die Isolationsstufe SERIALIZABLE : Sie stellt sicher, dass jede parallele Ausführung der Transaktionen auch zu einem guten Zustand führt, wenn unabhängig voneinander nacheinander ausgeführte Transaktionen zu einem guten Zustand führen. Glücklicherweise hat Michael Cahill in seiner Dissertation herausgefunden, wie die SERIALIZABLE- Isolationsstufe mit geringem Aufwand von Snapshot-isolierten Datenbanken unterstützt werden kann.

Wenn bei Verwendung einer SERIALIZABLE- Isolationsstufe in einer isolierten Snapshot-Datenbank zwei Personen gleichzeitig versuchen, die gespeicherte Prozedur auszuführen und sich gegenseitig auf die Zehen treten, wird eine der Transaktionen abgebrochen.

Unterstützt SQL Server nun wirklich die Isolationsstufe SERIALIZABLE (anstatt die Snapshot-Isolation hinter dem Schlüsselwort SERIALIZABLE zu maskieren )? Ehrlich gesagt weiß ich nicht: Die einzige mir bekannte Datenbank, die dies unterstützt, ist PostgreSQL.

Obwohl ich keine SQL Server-spezifischen Ratschläge gegeben habe, veröffentliche ich diese Antwort dennoch, da Benutzer von PostgreSQL und Benutzer anderer Datenbanken, die einen Wechsel zu PostgreSQL in Betracht ziehen können, von meiner Antwort profitieren können. Benutzer von Nicht-PostgreSQL-Datenbanken, die nicht zu PostgreSQL wechseln können, können ihren bevorzugten Datenbankanbieter unter Druck setzen, eine echte SERIALIZABLE- Isolationsstufe anzubieten .

juhist
quelle
Ich nehme an, das Downvote bedeutet, dass jemand untersucht hat, ob SQL Server die Isolationsstufe SERIALIZABLE hat, und herausgefunden hat, dass dies nicht der Fall ist.
Juhist
-2

Mir ist klar, dass das „echte“ Problem komplexer sein kann.

Falls dies nicht der Fall ist: Wenn Sie Ihre Archivierung mit Einfüge- und / oder Aktualisierungsauslösern durchführen, können Sie das Problem vermeiden, das Sie lösen möchten.

Hoffe das hilft,
-Chris C.

J. Chris Compton
quelle
1
Was meinst du mit "sofort"? Unmittelbar nach was? Nach dem Einfügen? Sobald eine neue Zeile eintrifft, wird sie sofort an das Archiv gesendet? Oder meintest du nach dem Update? Jede Datenänderung löst also eine Archivierung aus? Vielleicht sollten Sie genauer wissen, in welchem ​​Szenario Sie dies vorschlagen.
Andriy M
Die Archivierung kann zu kostspielig und / oder zu selten erwünscht sein, um bei jeder Einfügung durchgeführt zu werden, insbesondere wenn die Quelltabelle häufig eingefügt wird und / oder die Transaktionssicherheit zwischen ihr und dem Archiv teure Sperren erfordern würde.
underscore_d
@underscore_d Ja, es kann zu teuer sein oder ist nicht immer erforderlich. Deshalb habe ich meine Antwort mit der Aussage begonnen, dass the 'real' problem may be more complex. Falls dies nicht der Fall ist, sind Trigger eine gute Lösung. Außerdem ist es wahrscheinlich einfacher zu testen und zu warten, da es sich um eine Funktion der Datenbank handelt und nicht um eine benutzerdefinierte Lösung.
J. Chris Compton
@AndriyM Ich habe das Wort sofort entfernt und durch einen Verweis zum Einfügen / Aktualisieren von Triggern ersetzt. Entschuldigung für die Verwirrung.
J. Chris Compton
1
Ich habe die Frage erneut gelesen und denke, ich kann die Quelle meiner Verwirrung erkennen. Was Sie hier vorschlagen, ähnelt eher der Prüfung als der Archivierung. Nach meinem Verständnis bedeutet das Archivieren von Daten das Verschieben der Daten (z. B. von einer Tabelle in eine andere). Obwohl das OP die Funktion seiner Prozedur als "eine Art Archivierung" zusammenfasste, sagten sie nie, dass die Daten aus der Quelle entfernt würden, sondern nur, dass sie daraus ausgewählt und in das Ziel eingefügt würden. Ich nehme an, Sie haben angenommen, dass das OP seine Daten kopieren und nicht verschieben muss. In diesem Fall ist die Verwendung von Triggern wahrscheinlich sinnvoll.
Andriy M