# parallele Zugriffe auf die gleichen Datenbankeinträge



## JanHH (15. Dez 2011)

Hallo,

tut mir ja fast leid, das mal wieder zu fragen, aber mir ist das immer noch unklar, aber ich brauch das jetzt demnächst..

Wenn zwei Transaktionen die gleichen Datenbankzeilen verändern wollen, wie bekommt man das dan synchronisiert? Handelt sich dabei optimalerweise um JPA 1.0, aber auch "natives hibernate" oder nofalls SQL ist ok.

Anwendungsbeispiel: Ein simpler Zähler, der bei Aufruf einer Webseite inkrementiert wird. Da können ja mehrere Zugriffe parallel stattfinden. Aber jeder Aufruf soll wirklich SEINEN eindeutigen Zählerstand bekommen, also schön synchronisiert erhöhen.

Nach meinen Kenntnisstand kann man Zeilen mit SELECT FOR UPDATE locken und wenn eine Transaktion dann versucht, eine  Zeile zu schreiben, die bereits von einer anderen gelockt war, klappt das nicht, es gibt ein rollback und eine exception.

Und auch sonst (ich kenn mich damit wirklich nur oberflächlich aus) basieren alle Möglichkeiten, das irgendwie mit Locking zu machen, darauf, dass eine der beiden Transaktionen bei Kollisionen nicht ausgeführt, ge-rollback-ed und eine Exception ausgelöst wird.

Aber das hilft ja bei der Anwendung nicht weiter. Wenn eine Exception ausgelöst wird, wurde ja kein Zählerstand ermitelt für diesen Aufruf. Was also tun? Erneut versuchen bis es irgendwann klappt? Also quasi Endllosschleife, die immer wieder probiert und dann abbricht wenns funktioniert hat?

oder wie löst man sowas korrekt?

Gruß+Danke Danke
Jan


----------



## fastjack (15. Dez 2011)

Was meinst Du den mit Zählerstand? ID vielleicht. Dafür kannst Du auch eine Sequenz oder irgendetwas mit auto_increment benutzen, dann ist von Haus aus synchron. 

Endlosschleife ist ein bisschen heftig, geht aber auch, wenn Du andere SQL-Fehler korrekt behandeln/weiterleiten kannst.


----------



## robertpic71 (16. Dez 2011)

Zuerst einmal: Datenbanken sind verflucht schnell, eine einfache Updateoperation dauert nur wenige Millisekunden und weniger (oftmals 0 ms). 

Um jetzt bei dem einfachen Beispiel "Zugriffszähler einer Webseite" zu bleiben. Selbst eine langsame Datenbank sollte über 50 Transaktionen (pro Sekunde) dieser Sorte schaffen. 

Bei voller Last gibt es natürlich gleichzeitige Anfragen. Damit es trotzdem funktioniert, gibt es bei jeder Datenbank (sinngemäß) ein* "row locking timeout"*. Überlicherweise sind diese relativ hoch eingestellt (> 30 Sekunden). D.h. wenn man den Satz bei SELECT FOR UPDATE nicht gleich bekommt, versucht die Datenbank den Satz noch innerhalb dieses Timeouts zu bekommen. Die Anforderungen stellen sich dann *"praktisch" hintereinander an* und werden nacheinander abgearbeitet. Auf diese Weise läßt die Datenbank ohne Exception bis zur ihren Grenzen auslasten.

Gerade der Zähler ist einfach - und auch in einer Transaktion abzuhandeln. Bei größeren Transaktionen kann es komplizierter werden. Es können auch einfache Transaktionen problematisch werden, wenn zwischen SELECT (FOR UPDATE) und UPDATE gewartet werden muss. 

Damit die Lockzeiten kurz bleiben, kommen dann Techniken wie "optimistic locking" zum Einsatz.

Ich weiß nicht wie "tief" du in die Materie eindringen willst, dass war mal eine "kleine" Einführung.


----------



## JanHH (16. Dez 2011)

Danke danke.

@fastjack: Der Zähler war ja nur ein Beispiel, ging halt um irgendwas derartiges, wo sowas autfritt. In der Anwendung gibts diverse Stellen, wo das problem auftreten kann.

@robertpic71: Vielen Dank, klingt sehr aufschlussreich. Wenn das so stimmt was Du schreibst bedeuet das, dass de Datenbank SELECT FOR UPDATE-Statements automatisch synchronisiert? Also auch den gesamten Bereich zwischen Anfang und Commit? geht ja in etwa um

1. transaction begin
2. select irgendeine zeile for update
3. update  zeile
4. transaction commit

Klappt das dann?

Also sagen wir mal, es gibt in der tabelle eine Spalte A und dort gibts drei mal den Wert 1. Drei Zeilen mit A=1. Jede Transaktion will eine 1 selecten und durch eine 2 ersetzen. Ist dadurch dann sichergestellt dass die erste Transaktion dann eine der drei zeilen erwischt, eine andere, die mehr oder weniger gleichzeitig läuft, dann definitiv eine andere? Was Du schreibst, klingt so..

Als Datenbank kommt übrigens Postgresql 8.4 zum Einsatz, kann aber an sich auch noch eine andere Datenbank werden, oder auch verschiedene (je nach Kunde).

Wie tief in die materie eindringen.. gute Frage. Bin quasi der Chefentwickler einer eher komplexen Webanwendung mit intensivem Datenbankzugriff und Performance-Aspekten. Wird wohl noch kompliziert werden.


----------



## JanHH (16. Dez 2011)

Also, nochmal etwas konkreter: Angenommen, es gibt 10 Zeilen von A mit Wert 1 (und jede Menge Zeilen mit Wert 2). Dabei könnte die 1 z.b. für "frisch" und die 2 für "schon mal gelesen" bedeuten. Jeder Zugriff soll nun genau eine Zeile aus der Datenbank mit A=1 erhalten.

Also ein SELECT FOR UPDATE where a=1 mit einem Limit der Anzahl Resultate auf 1, danach ein update von a auf Wert 2, danach commit.

ist damit dann sichergestellt, dass jeder Zugriff eine individuelle Zeile aus der Datenbank mit A=1 erhält, ohne dass weitere Synchronisation notwendig wäre? Würde sagen, ja?


----------



## JanHH (16. Dez 2011)

Und noch eine Frage: SELECT FOR UPDATE WHERE A=1 lockt ALLE Datenbankzeilen wo A=1, oder?

Wenn man da nun ein Limit von nur einem Result setzt, wird dann auch nur eine Zeile gelockt?


----------



## fastjack (16. Dez 2011)

> Zuerst einmal: Datenbanken sind verflucht schnell,


Das hat bei einem Mehrbenutzersystem relativ wenig zu sagen, da es um gleichzeitige Zugriffe geht.


----------



## maki (16. Dez 2011)

Suchbegriffe: 
optimistisches Locking (zB. durch eine Versionnummer erreicht)
pessemistisches Locking (Datensatz wird gesperrt durch SELECT FOR UPDATE)

Du scheinst diese beiden Varianten durcheinanderzuwürfeln 

Dann gibt es noch die sog. "offline Varianten", da händelt die Application und nicht die Db das locking (optimistisch oder pessemistisch), geht natürlich nur wenn nicht mehrere Apps auf die Db zugreifen.


----------



## robertpic71 (16. Dez 2011)

JanHH hat gesagt.:


> 1. transaction begin
> 2. select irgendeine zeile for update
> 3. update  zeile
> 4. transaction commit
> ...


Benutzer A
1A. transaction begin
2A. select irgendeine zeile for update
3A. update  zeile
4A. transaction commit

Benutzer B
1B. transaction begin
2B. select irgendeine zeile for update
3B. update  zeile
4B. transaction commit

Was die Datenbank für dich macht:
1A, 2A, 1B
Wenn der Benuter B (Aktion 2B) jetzt die gleiche(n) Zeile(n) sperren will, dann wartet die Datenbank 
3A + 4A ab - ohne eine Exception zu werfen. Wie lange zugewartet wird hängt von diesem "row locking timeout" ab.

Nach 1A, 2A, 1B
käme dann:
2B - wartet
3A
4A
2B - bekommt den Satz
3B
4B

Was die Datenbank *nicht* für dich macht:
- ist einen anderen Satz auszuwählen, weil der erste gesperrt ist.
Bei deinem Beispiel mit den 3 Sätzen A=1, würde jeder SELECT immer den gleichen bzw.
die gleiche Sätze liefern.

Wenn du eine größere Webanwendung machst, wirst du mit dem hier beschriebenen "Pessimistic Locking" (den Satz von Anfang bis zum Ende sperren) sowieso nicht durchkommen.
Sobald der Benutzer "dazwischen" ist (erschwerend noch über eine zustandslose webverbindung)
kann man das vergessen.

Mit optimitic locking schaut das da so aus:
1. transaction begin (readonly oder ohne)
2. select irgendeine zeile 
2b. versionsfeld (zählerid oder timestamp) von satz merken
3. transaction commit/ende
4. Ausgabe der Daten an den Benutzer 
4b. Verabeitung der Benutzereingaben
5. transaction begin
6. select irgendeine zeile for udpate
7. vergleich versionsfeld von 2b mit 6 (bei ungleich --> Fehlermeldung Satz wurde verändert)
8. update  zeile
9. transaction commit

Bei komplexen Datenbankanwendung "rechnet" sich dann schnell ein Persistenzframework
ala Hibernate. Das managed die Versionskontrolle für dich.  



			
				maki hat gesagt.:
			
		

> Du scheinst diese beiden Varianten durcheinanderzuwürfeln


Ich würfle hier gar nichts durcheinander. Ich habe das als mögliche Option zur Verkürzung der Lockzeiten angeführt.

zur DB-Geschwindigkeit


			
				fastjack hat gesagt.:
			
		

> Das hat bei einem Mehrbenutzersystem relativ wenig zu sagen, da es um gleichzeitige Zugriffe geht.


Die Verarbeitungsgeschwindigkeit ist sehr wohl auch für Mehrbenutzersysteme relevant. Im Endeffekt müssen (sperrende) Zugriffe auf gleiche Sätze immer sequentiell (von der Datenbank) abgearbeitet werden.  Solange die einzelnen Transaktionen schnell abgearbeitet werden, bleibt der "parallel arbeitende" Eindruck erhalten. Mir ging es vor allem darum, den Kontrast aufzuzeigen: Eine einfache DB-Operation dauert wenige Millisekunden, die Default-Lock-Wartezeiten liegen dagehen sehr hoch (MySQL 50 Sekunden, Oracle&DB2: 60 Sekunden).  

Das es gesperrte Sätze in einer Multiuserumgebung gibt, ist normal. Bei einer richtig designten Anwendung und einer ausreichend schnellen Datenbank, bleiben Wartezeiten im Millisekundenbereich und für Anwender und Applikation unsichtbar.


----------



## maki (16. Dez 2011)

> Ich würfle hier gar nichts durcheinander. Ich habe das als mögliche Option zur Verkürzung der Lockzeiten angeführt.


Du warst nicht gemeint, sondern JanHH.
Hätte zitieren sollen dann wäre es klar gewesen.


----------



## JanHH (17. Dez 2011)

Hm. Nach dem was ihr schreibt scheint mir ein Synchronisieren innerhalb der Applikation fast das beste zu sein.

Ich muss wohl noch mal etwas konkreter auf die konkrete Anwendung eingehen.

Es gibt eine Reihe Datensätze (1 Datensatz = 1 Zeile in DB), und jeder benutzer soll einen davon zum Bearbeiten bekommen. Jeder Datensatz soll auch nur einmal zum Bearbeiten einem Benutzer vorgelegt werden. Dazu gibt es eine Attribut "selected", am Anfang false bei allen Datensätzen.

Es soll nun also sichergestellt werden, dass auch bei parallelen Zugriffen, quasi-gleichzeitig (was in der Praxis wohl so gut wie nie vorkommt, aber es muss halt zugesichert IMMER funktionieren), zwei Benutzer immer verschiedene Datensätze bekommen.

Synrhonisation mit Java ist da natürlich banal.. eine synchronized-Methode, die einen Datensatz mit selected=false sucht, auf true setzt und zurückliefert. Aber wie würdet ih das innerhalb der Datenbank synchronisieren?

Dazu kommt noch eine weitere anforderung.. Adminisratiosseitig ist es ebenfalls möglich, den selected-Status von Datensätzen manuell zu ändern (z.B. "Datensatz soll doch noch mal erneut vorglegt werden -> auf false setzen" oder "datensatz soll gar nicht mehr vorgelegt werden -> auf true setzen"). Hierbei tritt das Problem natürlich auch auf. Wenn ein Administrator manuell auf einen Datensatz zugreift, darf das ja auch nicht kollieideren.

Also was tun? Ich brauch da relativ zügig eine Lösung für. Bisher wird alles javaseitig synchronisiert, aber das beschriebene Szenario ist nur ein vereinfachendes Beispiel, es ist in Wirklichkeit deutlich komplexer und wenn eine einfache Möglichkeit zur Synchronisation dieser Dinge innerhalb der Datenbank existiert, würde das die Anwendung wohl deutlich einfacher und auch performanter machen.

Ich dachte allerdings auch.. solche Anwendungsfälle sind so häufig, dass es dafür doch auch einfache standard-Lösungen geben muss.


----------



## JanHH (25. Dez 2011)

Hab nochmal weiter drüber nachgedacht (und gelesen).. korrekt erscheint mir die Version: Optimistic Locking mit Versionsfeld. Oder?

Da kommt dann allerdings auch wieder die Endlosschleife ins Spiel.

Also im Kern gehts bei dem OL mit Versionsfeld doch darum: T1 will Datenbankeintrag ändern, aber in der Zwischenzeit hat schon T2 den gleichen Eintrag geändert -> Inkonsistenz bei der Versionsnummer, T1 bekommt eine Exception beim Versuch zu commiten. Oder?

Beim Thema "User soll einen Datensatz zugewiesen bekommen": T1 sucht Datensatz, findet, will ihn als "ausgewählt" markieren, T2 hat das aber in der Zwischenzeit bereits getan, T1 also Pech, neu suchen. Bis irgendwann einer gefunden wurde oder man das ganze abbricht (timeout).

Wenn man das durchgehend so implementiert können alle möglichen Teile der Anwendung problemlos parallel auf die gleichen Daten zugreifen, ohne jegliche Synchronisation innerhalb der Applikation, und ohne dass es überhaupt zwingend Teile der gleichen Awendung sein müssen, oder?

Oooooder?

maki: gibts da irgendein schlaues Buch, quasi Standardwerk, zwecks Einarbeitung in die Materie?

Danke+frohe Weihnachten
Jan


----------



## Marcinek (25. Dez 2011)

In 'THE COMPLETE DATABASEBOOK' solltest du die Konzepte finden um OL und PL sowie weitere Lockingverfahren zu implementieren ... Außerdem zeigt es, wie dur dbms dabei helfen.

Link kommt, wenn ich sm pc bin  aber an sollte das Buch so finden

--

Wieso es bei OL zu einer endlosschleife kommen soll, kann ich gerade nicht nachvollziehen .  Er muss ex hoechstens paar myl probieren, bis er schnellgenug ist, oder deine locking domainen sind zu gross und umfassen zuviele relationen  in diesem fall sollte man PL nschdenken., damit der user weiss uhhh hier kann ich ehh nix machen.

Oder die datensätze mergen. 

Es gibt einfach soviele Möglichkeiten :shock:

---

Ich habe gerade noch deinen anderen Thread gelesen. Und ich glaube das Problem liegt nicht in der Synchronisation, sondern bei der Programm Architektur.

1. 



JanHH hat gesagt.:


> Synrhonisation mit Java ist da natürlich banal.. eine synchronized-Methode, die einen Datensatz mit selected=false sucht, auf true setzt und zurückliefert. Aber wie würdet ih das innerhalb der Datenbank synchronisieren?



Das ist überhaupt nicht banal. In einer Adressverwaltung mit 10 Usern und EINER Serverinstanz ist das banal, was jedoch, wenn du 20 User und zwei Server hast -.-. 

2. 

Du hast ein Datensatz und die Info "selected". Wenn du mehrere Datensätze via Round-Robin an die User verteilst, wieso merkst du dir nicht im Vorfeld, welchen Datensatz ein User bekommen soll? - Wieso muss hier der User (Problemmquelle Nr 1, weltweit) den Datensatz suchen.


----------



## JanHH (25. Dez 2011)

Danke fürs Buch..

denke auch nicht dass es da wirklich zu Endlosschleifen kommt, aber programmiertechnisch ist es halt eine.

```
while(true)
{
   boolean erfolg=versucheUpdate();
   if(erfolg || timeout)
      break;
}
[code=Java]
Aber halt vom Konzept her.. versuchen und wenns nicht geklappt hat nochmal versuchen, bis es dann irgendwann klappt.

Geht mir hier auch eher darum ob ich überhaupt richtig liege mit den Überlegungen. Muss eine komplexe Anwendung entwickeln und hab von Datenbanken relativ wenig Ahnung (wie man wohl merkt ;-) ).
```


----------



## Marcinek (25. Dez 2011)

Ach du ******** das geht ja in eine ganz falsche Richtung.

Was soll den dieses while(true) machen? Und ist das eine Client / Server geschichte oder ein FAT-Client? - Bei OL gibt es keine while schleifen.

Es gibt eine Exception und dann wird einfach nix gemacht.

Der User kann dann die Daten reloaden oder mergen lassen und dann nochmal speichern. Und dann klappt das auch. Du kannst bei OL nicht einfach 100 mal versuchen zu speichern, da deine Version eine ältere, als die in der DB ist.

Gruß,

Martin


----------



## JanHH (25. Dez 2011)

Doch ich denke schon dass das so richtig ist. Es handelt sich dabei auch nicht um eine klassische Webanwendung (zumindest nicht bei bestimmten Teilen).

Es gibt da diverse Prozesse, die vor und nach Beendigung bestimmte Datenbankzähler und sonstige Einträge ändern MÜSSEN, und zwar synchronisiert. "Exception und dann wird einfach nix gemacht" kommt nicht in Frage. Simples Beispiel ist ja der Zähler aus dem Einstiegsposting. Wenns da eine Exception gibt weil ein anderer Thread gerade den Zählern parallel schon geändert und committed hat, darf nicht dazu führen, dass nix weiter gemacht wird, sondern es muss dann halt erneut versucht werden, solange bis es geklappt hat.

Was Du schreibst, meine lokale Version sei immer älter als die in der Datenbank, stimmt ja nur, wenn sich meine Schleife innerhalb der Transaktion befindet. Es muss natürlich so sein

```
while(true)
{
   boolean erfolg=true;
   try
   {
      beginneTransaktion();
      mergeEntity();
      ändereEntity();
      committeTransaktion();
   }
   catch(Locking-Exception e)
   {
      erfolg=false;
   }

   if(erfolg || timeout)
      break;
}
```
Bedeuet natürlich dass man da mit container-managed transactions nix anfangen kann, aber das ist in dem Fall das kleinere Problem.

Anderes Beispiel.. Es gibt diverse Datensätze in der Datenbank. Jeder User bekomm einen Datensatz zum Bearbeiten vorgelegt (gehen wir z.B. mal davon aus dass es sich um Sachbearbeiter handelt, die die Datensätze bearbeiten sollen). Jeder Datensatz soll nur genau einmal vorgelegt werden. Es gibt in der Tabelle die Spalte "ausgewählt", die angibt, ob ein Datensatz schon vorgelegt wurde. Hier hilft doch auch nur, innerhalb einer Transaktion select Datensatz where ausgewählt=false, und dann ausgewählt auf true updaten, und wenns dabei dann eine OL-Exception gibt, hat sich parallel schon eine andere Transaktion den Datensatz geholt, also neuen Versuch starten. Auch hier kommt "Exception und dann passiert nix = User bekommt keinen Datensatz vorgelegt" nicht in Frage.

Oder versteh ich da irgendwas total falsch?

Die Anwendung ist, wie gesagt, untypisch. Bei einer typischen Anwendung hätte man eine Liste mit Datensätzen, User klickt auf einen, bekommt den, und den mittlerweile schon jemand anders bekommen hat, gibts eine Exception und der User bekommt die Liste erneut vorgelegt und kann sich was anderes aussuchen. Das ist bei meiner Anwendung aber anders. Hier gibts nur einen Button "nächsten Datensatz bitte", und dann soll einer aus der Menge der noch nicht bearbeiteten Datensätze ausgewählt und vorgelegt werden. Im Prinzip muss also der Schritt "User bekommt die Liste erneut vorgelegt und wählt einen anderen aus" automatisch vom Programm übernommen werden (daher - Endlosschleife bis es geklappt hat. Bei einer typischen Anwendung befindet sich ja sozusagen der User in der Endlosschleife, er probiert es bis er einen Datensatz bekommt, oder wg timeout (Harndrang, Kaffeebedürfnis ;-) ) abbrechen muss).


----------



## Marcinek (25. Dez 2011)

Deine while() - schleife ergibt keinen Sinn.

wenn die Entiät einmal nicht geupdatet werden konnte, kann sie beim nächstenmal ohne korrektur nicht geupdatet werden.

---

Nochmal die Frage: Was für eine Softwarearchitektur liegt hier vor( Client /Server, FAT?, WEB?)

Wieso kannst du nicht die Datensätze vorselektieren: Das ist doch dein Problem. Du machst ein select und weißt nicht, wer es selektiert.

Da hilft auch nix mit den Transaktionen. Die sind ja i.d.R. nur für das Schreiben da.

Ansosnten brauchst du eine Instanz, die die Datensätze selektiert und dann auch für alle das Flag "selektiert" setzt und dann wartet, bis einer diese abfragt und dann aus der Liste den zurückgibt.

Das ist aber schwachsinn, weil wenn der server neu gestartet wird, weiß man nicht mehr ob alle slektieren auch bearbeitet wurden.


----------



## JanHH (25. Dez 2011)

Webanwendung.. Also Seam und dazu noch zwei weitere Servlets.

Die while-Schleife macht Sinn, Deine Aussage, die entity kann auch beim nächsten Versuch nicht geupdatet werden, trifft nicht zu. Bzw. man muss die Anwendung da wohl noch genauer beschreiben. Wenn ich eine Version der Entity in der Session hab die ich dann mergen will, klappt das natürlich nicht. Wenn ich sie dann aber neu lade, den besagten Zähler oder ein Flag setze, und dann neu speichere, funktioniert das (hab ich ausprobiert).

Das mit der Instanz, die vorselektiert, wäre ja nix weiter als eine Synchronisation in irgendeiner Form in der Applikation und u.a. aus dem Grund den Du da angibst nicht praktikabel (mal abgesehen davon, dass das was ich schreibe auch nur eine vereinfachte Darstellung der Anforderungen ist, da es noch diverse weitere Möglichkeiten gibt, schreibend und lesend auf die Daten zuzugreifen). Abgesehen davon ist die Liste der noch freien Datensätze sehr dynamisch und daher kann eine temporär vorselektierte Liste auch innerhalb weniger Minuten oder sogar Sekunden wieder veraltet sein.

Beschreib Du doch ansonsten mal, wie Du einen simplen Zähler implementieren würdest (und bitte NICHT einfach mit einer Sequenztabelle in der Datenbank, das hilft mir wenig weiter, und auch nicht mit select for update).

Oder sagen wir mal, Zimmerreservierung im Hotel. User buchen Zimmer. Kann sein dass zwei User gleichzeitig buchen und muss auch sichergestellt werden dass die nicht versehentlich beide das gleiche Zimmer zugewiesen bekommen. Wie löst man sowas? Datenbanktabelle "Zimmer", zwei Spalten (zimmernummer und istFrei). Da wird doch auch ein freies Zimmer selected, istFrei auf false gesetzt, committed, und wens dann eine OL-Exception gibt, hat sich wohl parallel schon jemand anders das Zimmer geschnappt, also muss ein neues ausgewählt werden (-> Schleife solange bis es geklappt hat). Wie macht man denn sowas ganz konkret, wenn nicht so wie ich beschrieben hab????


----------



## Marcinek (25. Dez 2011)

Dann poste mal die DB Struktur.

[Edit]
1. Gui läd daten
2. User Selektiert ein Zimmer.
3. Bekommt eine OL Exception, weil der Datensatz, von jemand anders bereits geändert worden ist.
4. Gui läd daten neu und zeigt die neuen Daten an
5. Bei 2 gehts weiter.

Beachte, dass die Änderung sich auch auf andere Attribute der Relation beziehen kann. Das sind ganz simple stateless aufrufe. Da muss nix mit while() gemacht werden. Und schon garnicht auf dem Server.

Wenn es egal ist, welches Zimmer er bekommt, dann musst du ein paar Laden und dann synchronized zurück geben.

[/edit]


----------



## JanHH (25. Dez 2011)

Na damit bestätigst Du ja meinen Ansatz mit der Schleife, nur dass sich da halt der User in der Schleife befindet. "ein paar laden und dann synchronized zurück geben" ist keine brauchbare Lösung (z.a. da auch nix synchronized sein soll und weil noch diverse andere Leute ebenfalls auf die Daten zugreifen. Zum Beispiel der Hotel-Administrator, der manuell Zimmer auf frei oder belegt setzen kann).

also

2. Anwendung Selektiert ein Zimmer.
3. Bekommt eine OL Exception, weil der Datensatz, von jemand anders bereits geändert worden ist.
5. Bei 2 gehts weiter.


----------



## Marcinek (25. Dez 2011)

Wenn der Admin das macht, dann muss er die Version auch einen hoch machen und damit wäre das kein Problem.

Das Problem hier ist doch: Du vereinfachst das Problem auf ein "boolean selected". Und dann zeigst du hier eine vereinfachte Darstellung deiner Lösung.

Es gibt nach den Informationen, die du gibst keine andere Lösung. Und ka ob Optimistic Locking hierfür überhaupt gut ist.


----------



## JanHH (25. Dez 2011)

Es IST aber genau das Problem. Datensätze mit einem boolean selected und das Verhalten soll genau so sein wie bei dem Hotel-Beispiel was ich grad beschrieben hab. Und aus diversen anderen Gründen kommt keine Synchronisation in der Applikation, Vorselektierung oder derartige Lösungen in Frage.


----------



## Marcinek (25. Dez 2011)

Ja, dann mach das so.


----------



## JanHH (25. Dez 2011)

Ok ok.. bin ja auch gespannt obs dann klappt. Hast Du denn konkrete Gründe, warum man es NICHT so machen sollte? Ich finde, es spricht an sich nix dagegen.


----------



## Marcinek (25. Dez 2011)

Keine Ahnung, was das werden soll. 

IMHO ist die DB Struktur ungeeignet für das Problem.


----------



## JanHH (26. Dez 2011)

Dann schreib mir doch einfach mal wie Du das mit dem Zähler realisieren würdest. Jeder Webseitenzugriff soll den Zähler um eins erhöhen, es darf keiner verloren gehen, pessimistisches locking ist nicht erlaubt, synchronisierter Zugriff javaseitig ebenfalls nicht, und automatische Sequenz-Generierung der Datenbank auch nicht. Einfach eine Tabelle mit einem Eintrag, und jeder Zugriff soll den um eins erhöhen.


----------



## Marcinek (26. Dez 2011)

Es tut mir leid, aber Code nur gegen €.

---

Ich dachte aber, dass du dich für das OL entschieden hättest. Und hierfür bietet Hibernate ja eine Lösung an.


----------



## JanHH (26. Dez 2011)

achso


----------



## TheDarkRose (26. Dez 2011)

JanHH hat gesagt.:


> Dann schreib mir doch einfach mal wie Du das mit dem Zähler realisieren würdest. Jeder Webseitenzugriff soll den Zähler um eins erhöhen, es darf keiner verloren gehen, pessimistisches locking ist nicht erlaubt, synchronisierter Zugriff javaseitig ebenfalls nicht, und automatische Sequenz-Generierung der Datenbank auch nicht. Einfach eine Tabelle mit einem Eintrag, und jeder Zugriff soll den um eins erhöhen.



Und wer stellt solche schwachsinnigen Forderungen? :autsch:


----------



## JanHH (26. Dez 2011)

Ich, weil ich einfach wissen will, wie man sowas normalerweise löst.


----------



## fastjack (26. Dez 2011)

Wenn Du alles nicht machen darfst,nehme ich mal an, das auch ein auto_increment key ebenfalls verboten ist.

In diesem Fall würde bleibt wohl nur noch die brachial Lösung:


```
0) Drei Variablen, oldKey und actKey, boolean isError = false
1) max key holen, zwischenspeichern in actKey
    a) isError ?
        i) TRUE, oldKey != maxKey ? Abbruch : ansonsten weiter
        ii) FALSE, weiter
2) Datensatz updaten
    a) update gibt 0 Zeilen zurück oder Exception ist aufgetreten, isError=true und oldKey auf actKey setzen, dann gehe zu 1)
    b) ansonsten weiter
3) fertig
```

Eventuell würde ich einen Zähler einbauen, der die fehlgeschlagenen Versuche mitzählt, damit Du keine Endlosschleifen produzierst. 
Du kannst auch den key in einer Variable zwischenspeichern, sodaß Du ihn während der Fehlversuche vergleichen kannst. Also wenn Du immer wieder Exceptions bekommst oder keine Zeilen geupdatet wurden und alter key <> max key ist, ist etwas gewaltig faul. 

Aber das ist alles nicht 100% sicher, doch wenn Du soviel Techniken explizit nicht einsetzen darfst, was sollst Du dann noch machen???


----------



## JanHH (27. Dez 2011)

Entweder Zähler oder einfach timeout, klar. Endlos soll die Schleife nicht laufen. Meistens wirds ja eh beim ersten Mal klappen. So ganz verstehe ich Deinen Pseudecode allerdings nicht, aber dürfte in etwa auch dem entsprechen, was ich skizziert hab (in Worten: Versuchen, wenns nicht klappt, erneut versuchen, bis.. es geklappt hat oder abgebrochen wird).

Aber wieso sollte das nicht 100% sicher sein?


----------



## fastjack (28. Dez 2011)

Theoretisch kann es aus verschiedensten Gründen nicht klappen. Das Spektrum einer SQLException ist groß


----------



## JanHH (29. Dez 2011)

ich denke mal, da fehlt auch noch die praktische Erfahrung. Ich werd das halt mal so implementieren, dann schauen wir mal.

die Sachen mit dem Zähler lassen sich wohl auch noch einigermassen mit select for update lösen. mal schauen.


----------



## JanHH (30. Dez 2011)

Also mit dem Code hier gehts, falls es jemanden interessiert (etwas abgeändert ggü dem Original bei mir in der Anwendung)


```
MeinObjekt result=null;
		int count=0;
		boolean repeat=true;
		
		while((count<10) && repeat) // max 10 Versuche
		{
			em.getTransaction().begin();

			Query q=em.createQuery(...);
			q.setMaxResults(1);
			List l=q.getResultList();
			if(l.size()==0)
			{
				// gibt keins, abbrechen
				repeat=false;
				em.getTransaction().commit();
			}
			else
			{
				try
				{
					result=(MeinObjekt)l.get(0);
					result.setSelected(true);
					em.getTransaction().commit();
					repeat=false; // wenn wir bis hierher gekommen sind, ist alles ok; abbrechen
				}
				catch(Exception e)
				{
					count++;
					// OL-Exception, also nochmal
				}

			}
			em.clear();
		}
```


----------



## L-ectron-X (30. Dez 2011)

Marcinek hat gesagt.:


> Es tut mir leid, aber Code nur gegen €.


Hätte das Bill Gates gesagt, hatte ich es verstanden... :noe:


----------



## fastjack (30. Dez 2011)

Na und wenn der Kot, äh Code, aus Gold ist?


----------



## JanHH (31. Dez 2011)

So wie meiner? Der war aber gratis


----------

