Verwenden Sie die Datenbank, um das Sperren / Entsperren von Objekten zu verfolgen

8

Ich muss die Sperr- / Entsperraktionen von Objekten verfolgen. Vor jeder Aktion an einem Objekt (Vertrag, Partner usw.) wird ein lockEreignis ausgegeben. Nachdem die Aktion abgeschlossen ist, wird das unlockEreignis ausgegeben.

Ich möchte die Objekte erhalten, die gesperrt, aber noch nicht entsperrt sind. Ziel ist es, die Abfrage schnell zu gestalten und Deadlocks zu vermeiden.

Unten ist die Tabelle

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

Ich verwende die folgende Abfrage, um noch nicht freigeschaltete Objekte zu objektieren:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

Es funktioniert korrekt und Ergebnis a, bund d.

Meine Fragen sind: - Reicht meine Lösung aus, um Deadlocks zu vermeiden? Gibt es ein Problem, das auftreten kann, wenn INSERTwährend der Ausführung der Abfrage viele auftreten ? - Haben Sie eine andere (bessere) Möglichkeit, dies zu lösen?

AKTUALISIEREN

Ich entschuldige mich dafür, dass ich den Kontext nicht in die Frage gestellt habe. Das obige Datenbankdesign ersetzt nicht das Sperren von Datenbanken.

Wir haben ein externes System, das wir von unserem System aus nennen. Vor jeder Aktion, die an einem Objekt ausgeführt wird (dies kann ein Vertrag oder ein Partner sein), müssen die Systeme aufgerufen lockund unlockmethodisiert werden.

In letzter Zeit haben wir Situationen, in denen der Server abstürzt und wir ihn neu starten müssen. Leider hatten die bereits aufgerufenen laufenden Prozesse lockkeine Möglichkeit, unlockdie Objekte freizugeben, was zu mehreren anderen Problemen führte, wenn unser System erneut eine Verbindung zum externen herstellt.

Daher möchten wir die Möglichkeit bieten, jeden lockAnruf zu verfolgen . Nach dem Neustart des Servers rufen wir unlockdie zuvor gesperrten Objekte auf.

Vielen Dank an Remus Rusanu für den Hinweis, dass meine Frage einen DDL- Prototyp verwendet . Dies ist das erste Mal, dass ich eine Frage zu DBA poste und ich entschuldige mich dafür, dass ich die FAQ nicht gelesen habe.

Vielen Dank

Genzer
quelle

Antworten:

11

Ziel ist es, die Abfrage schnell zu gestalten und Deadlocks zu vermeiden.

Dies ist ein unrealistisches Ziel. Deadlocks werden von der Anwendung bestimmt, die die Sperren erwirbt, und haben keinen Einfluss darauf, wie Sie die Sperren implementieren. Das Beste, auf das Sie hoffen können, ist das Erkennen von Deadlocks.

Das Implementieren von Sperren als Datensatz ist problematisch. Zeilen bleiben bestehen und Sie werden beim Absturz der Anwendung Sperren verlieren, selbst wenn die Implementierung perfekt ist . Schlösser mit Applocks machen . Sie haben eine klare Transaktionssemantik und Deadlocks werden von der Engine erkannt.

Dennoch, mit Blick auf das, was Sie erreichen wollen, ist unwahrscheinlich , dass Sie benötigen Schlösser überhaupt . Sie beschreiben eine Warteschlange (wählen Sie das nächste zur Verarbeitung verfügbare Element aus und vermeiden Sie Konflikte => Warteschlange, nicht Sperren). Lesen Verwenden von Tabellen als Warteschlangen .

Was Ihre spezifische Implementierung betrifft (unter Verwendung einer Tabelle, die den Verlauf der Sperrung enthält), muss ich ehrlich sein: Es ist eine Katastrophe. Zunächst ist das Tabellendesign für die beabsichtigte Verwendung völlig unzureichend: Sie fragen nach dem Namen ab, aber die Tabelle ist ein Heap ohne Indizes. Es gibt eine Identitätsspalte ohne ersichtlichen Grund. Sie können antworten, dass es sich nur um eine 'Pseudocode'-Tabelle handelt, aber dies ist DBA.SE, Sie posten hier keine unvollständige DDL!

Noch wichtiger ist jedoch, dass die Implementierung keine Sperrung implementiert! Es gibt nichts zwei Benutzer daran zu hindern ‚Sperren‘ das gleiche Objekt zweimal. Ihre "Sperre" hängt ganz davon ab, dass sich die Anrufer auf magische Weise korrekt verhalten. Selbst eine am besten geschriebene Anwendung könnte diese Sperre nicht verwenden, da es keine Möglichkeit gibt, die Sperre atomar zu überprüfen und zu erwerben . Zwei Benutzer können überprüfen, daraus schließen, dass 'a' entsperrt ist, und gleichzeitig den ('a', 1)Datensatz einfügen . Ganz am wenigsten würden Sie eine eindeutige Einschränkung benötigen. Was natürlich die Semantik von "Zählen der Sperren vs. Entsperren, um den Status zu bestimmen" brechen würde.

Tut mir leid zu sagen, aber dies ist eine Implementierung der Klasse F.

AKTUALISIEREN

Daher möchten wir die Möglichkeit bieten, jeden Sperranruf zu verfolgen. Nach dem Neustart des Servers rufen wir die zuvor gesperrten Objekte entsperren auf.

Ohne eine verteilte zweiphasige Festschreibungstransaktion mit dem Remote-System können Sie nur eine "beste Anstrengung" unternehmen, da zwischen dem Schreiben der "Entsperrung" und dem tatsächlichen Aufruf der "Entsperrung" auf dem System eines Drittanbieters viele Wettbewerbsbedingungen bestehen . Hier ist meine Empfehlung:

  • Erstellen Sie eine einfache Tabelle, um die Sperren zu verfolgen:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • Fügen Sie vor dem Aufruf lockdie Sperre in Ihre Tabelle ein und legen Sie fest .

  • Nach dem Aufruf unlock löschen Sie die Sperre aus Ihrer Tabelle und schreiben fest
  • Schauen Sie sich beim Systemstart die Tabelle an. In jeder Zeile befindet sich eine Restsperre aus einem vorherigen Lauf, die freigeschaltet werden muss. Rufen Sie unlockfür jede Zeile auf und löschen Sie die Zeile. Erst nachdem alle ausstehenden Sperren "entsperrt" wurden, können Sie die normale Funktionalität der App wieder aufnehmen.

Ich schlage dies vor, weil der Tisch klein bleiben wird. Es enthält zu jeder Zeit nur aktuelle, aktive Sperren. Es wird nicht ad-nauseam wachsen und später aufgrund der Größe Probleme verursachen. Ist trivial zu sehen, was gesperrt ist.

Natürlich bietet diese Implementierung keinen Prüfverlauf darüber, was von wem und wann gesperrt wurde. Sie können dies bei Bedarf als eine andere Tabelle hinzufügen, in die Sie nur die Ereignisse einfügen (Sperren oder Entsperren) und diese niemals abfragen, um "verwaiste" Sperren herauszufinden.

Sie müssen immer noch auf Aufrufe zum Entsperren vorbereitet sein, um während des Startvorgangs fehlzuschlagen, da Sie nicht garantieren können, dass Ihr System nach dem Aufruf, unlocksondern vor dem Löschen der Zeile nicht abgestürzt ist (mit anderen Worten, Ihre Tabelle und das System eines Drittanbieters sind auseinander gerutscht und unterscheiden sich Versionen der Wahrheit). Auch hier können Sie dies ohne verteilte Transaktionen nicht verhindern, und ich würde niemals für DTC raten .

Remus Rusanu
quelle
4

Dadurch wird die Parallelität Ihrer Anwendung beendet. SQL Server verfügt bereits über alles, um zu vermeiden, dass dieselben Zeilen gleichzeitig aktualisiert werden. Dies ist also wirklich nicht erforderlich.

Deadlocks können aus verschiedenen Gründen auftreten. Ich empfehle daher, dass Sie diese Gründe untersuchen, bevor Sie Ihr eigenes Schließsystem erstellen. Sie können Deadlock-Diagramme aus der XE-Sitzung für den Systemzustand erfassen und mit deren Analyse beginnen.

Normalerweise sind Deadlocks das Ergebnis von Sperren für Objekte in unterschiedlicher Reihenfolge. Daher sollten Sie versuchen, Sperren immer in derselben Reihenfolge zu aktivieren. Es gibt andere Gründe, warum Deadlocks auftreten können. Allgemeine Hinweise sind jedoch, dass kurze Transaktionen Sperren für kurze Zeit halten. Daher ist es wahrscheinlich der beste Weg, Ihre Abfragen mit besserem Code, besseren Indizes und Divide-et-Impera-Strategien zu optimieren von Deadlocks.

Deadlocks vom Typ "Leser, die Autoren blockieren" können erheblich verringert werden, indem die Datenbank auf Read Committed Snapshot Isolation umgestellt wird . Wenn Ihre Anwendung mit Blick auf pessimistische Sperren erstellt wurde, sollten Sie alles sorgfältig prüfen und testen, bevor Sie diese Option in Ihrer Datenbank aktivieren.

Wenn Sie darauf bestehen, den Weg des "benutzerdefinierten Schließsystems" einzuschlagen, verwenden Sie mindestens etwas, das die Freigabe von Sperren garantiert, falls etwas mit der App schief geht. Möglicherweise möchten Sie die integrierte gespeicherte Prozedur sp_getapplock untersuchen, die etwas Ähnliches tut, wie Sie es anscheinend suchen .

UPDATE: Nach dem Lesen Ihrer bearbeiteten Frage ist dies eine alternative Möglichkeit, dieselbe Abfrage auszudrücken:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 1;

Es funktioniert, wenn Objekte nur einmal gesperrt werden dürfen, was ohnehin der springende Punkt des Schließsystems zu sein scheint.

spaghettidba
quelle