Sperren haben einen Lebenszyklus, und unterschiedliche Typen von Sperren sind auf verschiedene Arten mit anderen kompatibel. Sperren müssen in der richtigen Reihenfolge verarbeitet werden, um Deadlock-Szenarien zu vermeiden.
Aus den vorherigen Definitionen geht eindeutig hervor, dass eine S-Sperre schwächer ist als eine U-Sperre, weil sie beim Zugriff auf denselben Map-Eintrag mehr Transaktionen gleichzeitig ausgeführt werden können. Die U-Sperre ist geringfügig stärker als die S-Sperre, weil sie andere Transaktionen blockiert, die eine U- oder X-Sperre anfordern. Im gemeinsamen Sperrmodus werden nur solche Transaktionen blockiert, die eine X-Sperre anfordern. Dieser kleine Unterschied ist wichtig, um bestimmte Deadlocks zu verhindern. Die X-Sperre ist die stärkste Sperre, weil sie alle anderen Transaktionen blockiert, die versuchen, eine S-, U- oder X-Sperre für denselben Map-Eintrag anzufordern. Der Reineffekt einer X-Sperre ist die Gewährleistung, dass nur eine einzige Transaktion einen Map-Eintrag einfügen, aktualisieren oder entfernen kann und keine Aktualisierungen verloren gehen, wenn mehrere Transaktionen versuchen, denselben Map-Eintrag zu aktualisieren.
Die folgende Tabelle ist eine Kompatibilitätsmatrix für Sperrmodi, die Sie verwenden können, um festzustellen, welche Sperrmodi miteinander kompatibel sind. Informationen zum Lesen dieser Matrix: In der Zeile wird ein Sperrmodus angegeben, der bereits erteilt wurde. In der Spalte wird der Sperrmodus angezeigt, der von einer anderen Transaktion angefordert wird. Wenn in der Spalte das Wort "Ja" steht, wird der von der anderen Transaktion angeforderte Sperrmodus erteilt, weil er mit dem bereits erteilten Sperrmodus kompatibel ist. Das Wort "Nein" gibt an, dass der angeforderte Sperrmodus nicht kompatibel ist und die andere Transaktion warten muss, bis die erste Transaktion die gehaltene Sperre freigibt.
Sperre | Sperrtyp S (gemeinsam genutzt) | Sperrtyp U (aktualisierbar) | Sperrtyp X (exklusiv) | Stärke |
---|---|---|---|---|
S (gemeinsam genutzt) | Ja | Ja | Nein | Am schwächsten |
U (aktualisierbar) | Ja | Nein | Nein | Normal |
X (exklusiv) | Nein | Nein | Nein | Am stärksten |
Die vorherige Folge ist das klassische Deadlock-Beispiel, in dem zwei Transaktionen versuchen, mehrere Sperren anzufordern, und jede Transaktion die Sperren in einer anderen Reihenfolge anfordert. Um dieses Deadlock zu vermeiden, müssen die beiden Transaktionen die Sperren jeweils in derselben Reihenfolge anfordern. Wenn die optimistische Sperrstrategie verwendet wird und die Methode "flush" in der Schnittstelle "ObjectMap" von der Anwendung nie verwendet wird, werden Sperrmodi von der Transaktion nur während des Festschreibungszyklus angefordert. Während des Festschreibungszyklus legt eXtreme Scale die Schlüssel für die Map-Einträge fest, die gesperrt werden müssen, und fordert die Sperrmodi in Schlüsselreihenfolge an (deterministisches Verhalten). Mit dieser Methode verhindert eXtreme Scale die meisten der klassischen Deadlocks. eXtreme Scale kann jedoch nicht alle möglichen Deadlock-Szenarien verhindern. Es gibt ein paar Szenarien, die die Anwendung berücksichtigen muss. Im Folgenden sind die Szenarien beschrieben, die die Anwendung beachten und für die sie entsprechende vorbeugende Maßnahmen ergreifen muss.
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Lynn hat Geburtstag, also machen wir sie ein Jahr älter.
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
sess.commit();
In dieser Situation möchte Lynns Freund Lynn älter machen, als sie ist, und Lynn und ihr Freund führen diese Transaktion gleichzeitig aus. In dieser Situation halten beide Transaktionen eine S-Sperre für den Eintrag "Lynn" in der Map "PERSON", weil die Methode "person.get("Lynn")" ausgerufen wurde. Wegen des Aufrufs der Methode "person.put ("Lynn", p)" versuchen beide Transaktionen, die S-Sperre in eine X-Sperre zu aktualisieren. Beide Transaktionen werden blockiert und warten auf die Freigabe der S-Sperre durch die jeweils andere Transaktion. Demzufolge tritt eine Deadlock-Situation ein, weil eine zirkuläre Wartebedingung zwischen den beiden Transaktionen besteht. Eine zirkuläre Wartebedingung ergibt sich, wenn mehrere Transaktionen versuchen, eine schwächere Sperre auf eine stärkere Sperre für denselben Map-Eintrag hochzustufen. In diesem Szenario wird eine Ausnahme des Typs "LockDeadlockException" an Stelle einer Ausnahme des Typs "LockTimeoutException" ausgelöst.
Die Anwendung kann die Ausnahme des Typs "LockDeadlockException" für das vorherige Beispiel verhindern, indem sie die optimistische Sperrstrategie an Stelle der pessimistischen Sperrstrategie verwendet. Die Verwendung der optimistischen Sperrstrategie ist die bevorzugte Lösung, wenn die Map im Wesentlichen nur gelesen wird und nur wenige Aktualisierungen in der Map vorgenommen werden. Wenn Sie die pessimistische Sperrstrategie verwenden müssen, können Sie die Methode "getForUpdate" an Stelle der Methode "get" aus dem vorherigen Beispiel oder die Transaktionsisolationsstufe TRANSACTION_READ_COMMITTED verwenden.
Weitere Einzelheiten finden Sie im Abschnitt Sperrstrategien.
Die Verwendung der Transaktionsisolationsstufe TRANSACTION_READ_COMMITTED verhindert, dass die S-Sperre, die von der Methode "get" angefordert wird, gehalten wird, bis die Transaktion abgeschlossen ist. Solange der Schlüssel im Transaktionscache nicht ungültig gemacht wird, ist ein wiederholbares Lesen garantiert. Weitere Informationen finden Sie unter Sperrenmanager.
Eine Alternative zum Ändern der Transaktionsisolationsstufe ist die Verwendung der Methode "getForUpdate". Die erste Transaktion, die die Methode getForUpdate aufruft, fordert eine U-Sperre an Stelle einer S-Sperre an. Dieser Sperrmodus bewirkt, dass die zweite Transaktion blockiert wird, wenn diese die Methode getForUpdate aufruft, weil eine U-Sperre nur einer einzigen Transaktion erteilt wird. Da die zweite Transaktion blockiert ist, hält sie keine Sperre für den Map-Eintrag "Lynn". Die erste Transaktion wird nicht blockiert, wenn sie versucht, über den Aufruf der Methode "put" die U-Sperre in eine X-Sperre zu aktualisieren. Dieses Feature demonstriert, warum eine U-Sperre auch als aktualisierbarer Sperrmodus bezeichnet wird. Nach Abschluss der ersten Transaktion wird die Blockierung der zweiten Transaktion aufgehoben, und sie erhält die U-Sperre. Eine Anwendung kann das Deadlock-Szenario bei der Hochstufung von Sperren verhindern, indem sie die Methode "getForUpdate" an Stelle der Methode "get" verwendet, wenn die pessimistische Sperrstrategie verwendet wird.
Sie müssen erkennen, wenn eine Transaktion die Methode "getForUpdate" für mehrere Map-Einträge aufruft, um sicherzustellen, dass die U-Sperren von jeder Transaktion in derselben Reihenfolge angefordert werden. Angenommen, die erste Transaktion ruft die Methode "getForUpdate" für den Schlüssel 1 (key1) und die Methode "getForUpdate" für den Schlüssel 2 (key2) auf. Eine andere Transaktion ruft die Methode "getForUpdate" für dieselben Schlüssel, aber in umgekehrter Reihenfolge auf. Diese Folge löst die klassische Deadlock-Situation aus, weil mehrere Sperren in unterschiedlicher Reihenfolge von unterschiedlichen Transaktionen angefordert werden. Die Anwendung muss weiterhin sicherstellen, dass jede Transaktion beim Zugriff auf mehrere Map-Einträge die Schlüsselreihenfolge einhält, um diese Deadlock-Situation zu verhindern. Da die U-Sperre angefordert wird, wenn die Methode "getForUpdate" aufgerufen wird, und nicht, wenn die Transaktion festgeschrieben wird, kann eXtreme Scale die Sperranforderungen nicht wie im Festschreibungszyklus sortieren. Die Anwendung muss die Reihenfolge der Sperren in diesem Fall steuern.
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
boolean activeTran = false;
try
{
sess.begin();
activeTran = true;
Person p = (IPerson)person.get("Lynn");
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
person.flush();
...
p = (IPerson)person.get("Tom");
p.setAge( p.getAge() + 1 );
sess.commit();
activeTran = false;
}
finally
{
if ( activeTran ) sess.rollback();
}
Eine X-Sperre wird der Transaktion für "Lynn" erteilt, wenn die Flush-Operation
durchgeführt wird.
Eine X-Sperre wird der Transaktion 2 für "Tom" erteilt, wenn die Flush-Operation
durchgeführt wird.
Eine X-Sperre wird von Transaktion 1 für "Tom" während der Commit-Verarbeitung angefordert.
(Transaktion 1 wird blockiert und muss auf die von Transaktion 2 gehaltene Sperre warten.)
Eine X-Sperre wird von Transaktion 2 für "Lynn" während der Commit-Verarbeitung angefordert.
(Transaktion 2 wird blockiert und muss auf die von der Transaktion 1 gehaltene Sperre warten).
Dieses Beispiel demonstriert, dass die Verwendung der Flush-Methode eine Deadlock-Bedingung in der Datenbank und nicht in eXtreme Scale hervorrufen kann. Eine solche Deadlock-Bedingung kann bei jeder Sperrstrategie auftreten. Die Anwendung muss darauf achten, solche Deadlock-Bedingungen zu verhindern, wenn sie die Flush-Methode verwendet und wenn ein Loader in die BackingMap integriert ist. Das vorherige Beispiel veranschaulicht auch einen weiteren Grund, warum eXtreme Scale einen Wartezeitlimitmechanismus für Sperren hat. Eine Transaktion, die auf eine Datenbanksperre wartet, kann warten, während sie Eigner einer Sperre für einen eXtreme-Scale-Eintrag ist. Deshalb können Probleme auf Datenbankebene zu übermäßig langen Wartezeiten für einen eXtreme-Scale-Sperrmodus führen und zu einer Ausnahme des Typs "LockTimeoutException" führen.