2009-04-28 5 views
1

Ich schreibe einen Daemon, um die Erstellung neuer Objekte zu überwachen, die Zeilen zu einer Datenbanktabelle hinzufügt, wenn es neue Dinge erkennt. Wir rufen die Objektwidgets auf. Der Codefluss ungefähr folgendes:Verwenden einer Transaktion, um ein Rennen zu vermeiden

1: every so often: 
2: find newest N widgets (from external source) 
3: foreach widget 
4:  if(widget not yet in database) 
5:  add rows for widget 

Die letzten beiden Zeilen ein Rennzustand vorliegt, da, wenn zwei Instanzen dieses Daemon gleichzeitig ausgeführt werden, können sie sowohl eine Zeile für Widget X erstellen, wenn die Timing-Linien oben. Die naheliegendste Lösung wäre, eine unique Einschränkung für die Widget-ID-Spalte zu verwenden, aber das ist aufgrund des Datenbanklayouts nicht möglich (es ist eigentlich zulässig, mehr als eine Zeile für ein Widget zu haben, aber der Daemon sollte nicht Mach das nie automatisch.

Mein nächster Gedanke wäre, eine Transaktion zu verwenden, da dies für sie vorgesehen ist. In der Welt von ADO.NET glaube ich, dass ich eine Isolationsstufe von Serializable möchte, aber ich bin nicht positiv. Kann mir jemand in die richtige Richtung zeigen?

Update: Ich habe etwas experimentiert, und die serialisierte Transaktion scheint nicht das Problem zu lösen, oder zumindest nicht sehr gut. Der interessante Fall wird unten beschrieben und nimmt an, dass nur eine Tabelle beteiligt ist. Beachten Sie, dass ich über die Sperre Details nicht positiv bin, aber ich glaube, ich habe es richtig:

Thread A: Executes line 4, acquiring a read lock on the table 
Thread B: Executes line 4, acquiring a read lock on the table 
Thread A: Tries to execute line 5, which requires upgrading to a write lock 
    (this requires waiting until Thread B unlocks the table) 
Thread B: Tries to execute line 5, again requiring a lock upgrade 
    (this requires waiting until Thread A unlocks) 

Dies läßt uns in einer klassischen Deadlock-Bedingung. Andere Codepfade sind möglich, aber wenn die Threads A und B nicht verschachtelt werden, gibt es kein Synchronisationsproblem. Das Endergebnis ist, dass eine SqlException auf einen der Threads ausgelöst wird, nachdem SQL den Deadlock erkannt und eine der Anweisungen beendet hat. Ich kann diese Ausnahme abfangen und den speziellen Fehlercode erkennen, aber das fühlt sich nicht sehr sauber an.

Eine andere Route, die ich nehmen kann, besteht darin, eine zweite Tabelle zu erstellen, die vom Daemon gesehene Widgets verfolgt, wobei ich eine unique Einschränkung verwenden kann. Dies erfordert immer noch das Erfassen und Erkennen bestimmter Fehlercodes (in diesem Fall Verletzungen der Integritätsbedingung), so dass ich immer noch an einer besseren Lösung interessiert bin, wenn mir jemand einen einfallen lässt.

Antwort

2

Im Allgemeinen sollten Sie Transaktionen immer verwenden, wenn mehrere Prozesse oder Threads die Datenbank gleichzeitig verwenden.

Isolationsstufe "serialisierbar" sollte eigentlich funktionieren. Es erlaubt nicht, dass Daten, die von einer Transaktion gelesen werden, von einer anderen geändert werden. Es sperrt jedoch viel und sollte nicht generell verwendet werden, da es die Anwendung verlangsamt und das Risiko von Blockaden erhöht ist.

Alternativen:

  • Sie können nur die gesamte Tabelle, um sicherzustellen, sperren, dass niemand in sie schreibt, während Sie überprüfen, ob etwas (nicht) da ist. Das Problem ist, dass nur einer gleichzeitig in den Tisch schreiben kann, was bedeutet, dass dies alles sehr verlangsamt.(Sie können für die Daten suchen, wenn es nicht da ist, sperren die Tabelle und Suche wieder vor dem Einsetzen. Dies ist ein gemeinsames Muster.)
  • Ehrlich gesagt, sollten Sie darüber nachdenken, die Tatsache, dass zwei Transaktionen versuchen, Fügen Sie dieselbe Zeile gleichzeitig ein. Dies ist wahrscheinlich, wo Sie anfangen sollten, es zu lösen. Nur ein Daemon sollte für die gleichen Daten verantwortlich sein.
    • machen jeden Dämon seine eigene Datenverarbeitung
    • jeden Dämon machen einen Dienst anstelle der Datenbank aufrufen. Dort können Sie Dinge zusammenfügen, bevor Sie es einfügen.

By the way, müssen Sie eine eindeutige Kennung für die deutlich Daten, es trotzdem zu identifizieren. Wie können Sie in der Datenbank danach suchen, wenn Sie keine eindeutige Kennung haben?

+0

Danke für die Ideen. Leider denke ich, dass dies das Problem nicht lösen wird (ich werde es in der ursprünglichen Frage erklären). – Charlie

+0

In einer Konsistenz Sichtweise funktioniert es :-) aber es ist natürlich nicht gut, wenn es zu einer toten Sperren führt. Ich habe Angst, wenn ich nicht viel helfen kann, aber ich füge meiner Antwort einige Alternativen hinzu. –

+0

Nochmals vielen Dank dafür, dass Sie so viel darüber nachgedacht haben. Ich hatte auch die Service-Idee in Betracht gezogen, und ich denke, das wäre die ideale Lösung in meinem speziellen Fall (da ich dann mehr "standardmäßige" Sperr-Primitive wie Mutexe oder Monitore verwenden kann). Ich stimme zu, dass zwei Dämonen, wie ich beschrieben habe, keine gute Idee sind. – Charlie

0

Wie überprüfen Sie 'if (Widget noch nicht in der Datenbank)'. Wenn dies in sql in Form von "Select" geschrieben wird, können Sie "Select for update" verwenden, damit nur eine Daemon-Instanz dies gleichzeitig tun kann. Durch die Verwendung der Transaktion für die letzten beiden Zeilen und die Verwendung von "Select for Update" zum Sperren verhindern Sie das Rennen.

Ich bin sicher, es gibt ein Äquivalent in ado.net.

+0

Select. Weil Sie eine Zeile nicht sperren können, die nicht da ist. Wenn er die Reihe nicht findet, muss er dafür sorgen, dass niemand zur selben Zeit eindringt. –

0

Wenn Sie SQL Server 2008 (oder ein anderes DBMS, das dies unterstützt) haben, sollten Sie eine MERGE-Anweisung verwenden. Dies kann geschrieben werden, um eine Einfügung nur dann durchzuführen, wenn die Zeile nicht existiert.

0

(es ist eigentlich mehr als eine Zeile für ein Widget zu haben, erlaubt, aber der Daemon diese automatisch nicht immer tun sollte)

Dann nicht die Datenbank ist es, dass das Problem. Es ist die Tatsache, dass Sie mehrere Daemons haben, die nicht wissen, dass sie das gleiche Objekt gefunden haben. Es scheint mir, dass Sie hier Ihre Aufmerksamkeit konzentrieren müssen. Aus der Sicht der Datenbank reichen beide legitime Zeilen ein.

Was passiert, wenn Sie ändern passiert: für Update nicht funktioniert hier

1: every so often: 
2: find newest N widgets (from external source) 
3: foreach widget 
4:  if(widget not yet in database) 
5:  add rows for widget 

zu

1: every so often: 
2: while there are unhandled widgets 
2:  find first unhandled widget 
4:  if(widget not yet in database) 
5:   add one row for widget 
+0

Richtig, ich stimme dir zu. Ich wollte nicht sagen, dass die Datenbank in irgendeiner Hinsicht schuld ist, sondern nur nach einem Weg gesucht hat, die gesetzten Ziele zu erreichen. Ich denke, dass ich in der Lage sein werde, die Datenbank zu benutzen, indem ich eine Tabelle speziell für die Daemons erstelle, wo ich die Eindeutigkeit erzwingen kann. – Charlie

+0

Ich bin mir immer noch nicht sicher, ob ich sehe, wie man ein legitimes Duplikat von einem unrechtmäßigen erkennen kann. Aber siehe Bearbeiten zu meiner Antwort. – dkretz

Verwandte Themen