Dwie transakcje - saveAndFlush, a długość requestu.

Dwie transakcje - saveAndFlush, a długość requestu.
Bambo
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Postów:779
0

Cześć, mam taką zagwozdkę w temacie relacyjnych baz i transakcji.

Mam transakcję T1, która pobiera z repo counter (10), mnoży go x2 (20), zapisuje, ale metodą saveAndFlush, aby update poszedł od razu, po czym czeka załóżmy 5s (bo np jest jeszcze jakaś długa operacja biznesowa) i dopiero się commituje.

W międzyczasie (ale po tym jak fizycznie poszedł update countera (20) w T1) transakcja T2 pobiera counter (jest 10, bo izolacja READ_COMMITED), mnoży go x3 (30) i zapisuje do DB - i to jest zanim T1 się zakomituje, bo T2 nie ma żadnych długich operacji.

No i w wyniku mamy counter = 30. No i tu jest ok, ale zastanawiam się dlaczego request http, który zrobił T2 kręci się aż do czasu kiedy skończy się T1? W logach widzę, że T2 robi sql z updatem countera na 30 dużo wcześniej. Czy ta T2 musi z jakiegoś powodu czekać na commit T1? Czemu tak jest?

I dodatkowo jeśli mam w T1 saveAndFlush, a na jej końcu rzucę jakiś RuntimeException to rollback tej transakcji coś zrobi w ogóle? Odkręci ten update jak rozumiem?

Jeśli T1 nie ma flusha tylko normalny save, to request z T2 trwa ułamek sekundy, ale counter na końcu jest 20, bo T1 puszcza update do bazy na samym końcu transakcji i to jest dla mnie zrozumiałe.

edytowany 3x, ostatnio: Bambo
PI
Niezły temat, ciekaw jestem czy ktoś będzie w stanie odpowiedzieć
AK
jeden ze scenariuszy skomplikowanych w obiektówce/JPA, dziecinnych w SQL
IV
@AnyKtokolwiek: może tak, ale obecnie logiki biznesowej nie pisze się na bazie danych, tylko np. w językach obiektowych, więc na punkcie styku tych dwóch technologii nie zawsze jest to dziecinnie proste, mimo, że osobno te technologie mogą być dziecinnie proste.
Charles_Ray
@AnyKtokolwiek: a co tutaj zmienia czy jest JPA czy nie?
IV
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 miesiące
  • Postów:30
1

Bawiąc się JPA poczytaj o adnotacji @Version, optimistic locking, pessimistic locking, no i jak to od strony bazy danych wygląda, czyli taki np. select for update.

Bambo
Ale ja to wiem jak to działa o_O. Temat jest troszkę o czym innym. No chyba, że jednak nie ? ;p Ale tak jak pisałem- debugowalem SQL pod spodem. A locków akurat tu nie mam. Mowa o transakcjach.
IV
Prawdopodobnie masz założoną blokadę na wierszu w bazie a to wiąże się z tym co wcześniej napisałem.
IV
SQL debugowałeś po stronie aplikacji czy bazy danych? To, że aplikacja wysłała zapytanie to nie znaczy, że akurat zostało wykonane.
Bambo
No logi hibernate mówiące jakie wysyła SQL. I nie ma żadnego pesymistic locka bo nie widzę selekt for update. Czyli może być tak, że ta sqla poszła później?
Charles_Ray
Wysłanie SQL != commit transakcji, podstawy! :) Jak to nie masz locków - to tak jakbyś powiedział, że nie płacisz podatków :)
Charles_Ray
  • Rejestracja:prawie 17 lat
  • Ostatnio:około 4 godziny
  • Postów:1873
6

Trzeba by zobaczyć od strony bazy danych jak wyglądają locki, natomiast saveAndFlush nie commituje transakcji, a jedynie odpala zapytania (zrzuca zmiany z cache JPA).

To tak jakbyś w konsoli bazy napisał „begin transaction”, zrobił jakiś update, ale nie zrobił „commit”. W konsekwencji na wierszach (a nawet na całej tabelce, jeśli jest TABLOCK) założony jest lock i pozostałe transakcje (w zależności od stopnia izolacji) będą lub nie czekać na zakończenie pierwszej.


”Engineering is easy. People are hard.” Bill Coughran
edytowany 2x, ostatnio: Charles_Ray
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4706
5

Zasadniczo to co opisujesz to ani JPA, ani SQL. To kwestia konkretnego silnika bazy danych. Warto poćwiczyć na gołej konsoli SQL (najlepiej natywnej dla db).
Jak już potwierdzisz to szukasz pod hasłem concurrency database XXX (mvcc itp), locking i zwykle takie scenariusze są opisane. Jak spędzisz kilka dni z dokumentacją i testując kolejne przypadki to pewnie nawet poczujesz, że ogarniasz czemu tak się dzieje. A kilka tygodni później i tak zapomnisz :-) (swego czasu trochę spędziłem analizując różne podobne przypadki Postgresa (są dobrze opisane w necie) - ale to było dawno i nieprawda).


jeden i pół terabajta powinno wystarczyć każdemu
Bambo
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Postów:779
0

@Charles_Ray: nie wiem w którym miejscu zostałem źle zrozumiany, ale ja nigdzie nie napisałem, że transakcja mi się commituje przy saveAndFlush a ponad to przy tym jak leci sql do bazy danych ;p

Może, przedstawie to na jakimś diagramie:

screenshot-20210818213207.png

I teraz .. w którym miejscu popełniam błąd myślowy:

Dla T1:
findById = select po obiekt
saveAndFlush = update counter
I koniec T1 to jest commit

Dla T2:
findById = select, ale dostajemy 10 bo READ_COMMITED
save = update ALE DO CACHE JPA
Koniec T2 to już zrzucenie update do bazy

Moje pytanie ...
Czy i dlaczego jeśli tak jest T2 czeka na commit T1 aby, się skommitować?

W logach hibernatowych update do bazy w T2 idzie dużo wcześniej przed końcem T1, a mimo wszystko request T2 kręci się aż do końca requestu T1.

Na koniec counter jest 30.

Baza to h2, bo robię takie ćwiczenia po prostu

@jarekr000000
Dzięki, w wolnej chwili poćwiczę na konsoli sobie jakieś psqlowej i przypomnę księge do sql.

edytowany 1x, ostatnio: Bambo
Charles_Ray
T2 nie może odczytać wiersza modyfikowanego przez T1, bo masz READ_COMMITED - T2 musi zaczekać, żeby nie operować na danych, które mogą być rollbacknięte. Taki poziom izolacji wybrałeś, żeby tego właśnie uniknąć kosztem spadku wydajności.
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
3

Wydaje mi się że masz locka na counterze który jest zwolniony dopiero po koncu transakcji.


Bambo
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Postów:779
0

@ProgScibi:

Nigdzie nie idzie mi select for update, nigdzie nie użyłem adnotacji @lock. Ponad to T2 może mi bez problemu odczytać counter i go zmienić, więc gdzie tu lock?
Nie mówię, że go nie ma, bo może tak jest, ale nie rozumiem skąd on tam i jak on w takim razie działa skoro pozwala bawić się counterem bez problemu. Są takie locki, które pozwalają na wszystko?

Ja znam optimistic locking związany z @Version oraz pesisimistic locking związany właśnie z w/w adnotacją @lock robiący select for update w zapytaniu. Chyba, że coś grubo pomieszałem.

edytowany 1x, ostatnio: Bambo
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
3

@Bambo:

Nigdzie nie idzie mi select for update, nigdzie nie użyłem adnotacji @lock.

Ponad to T2 może mi bez problemu odczytać counter i go zmienić, więc gdzie tu lock?

zachęcam do przeczytania o poziomach izolacji. W read committed jedna transakcja nadpisuje wiersz dopero jak inna się skończy. Read committed jest od tego żebyś ne czytał zmian które jeszcze nie zostały zacommitowane. No i są różne rodzaje locków

Zacytuję z książki:

Transactions running at the read committed isolation level must prevent drity writes, usually by delaying the second write until the first write's transaction is commited or aborted


edytowany 4x, ostatnio: scibi_92
Charles_Ray
  • Rejestracja:prawie 17 lat
  • Ostatnio:około 4 godziny
  • Postów:1873
4

@Bambo Musisz zrozumieć, że silnik bazy danych zakłada locki na wierszach czy tego chcesz czy nie, to jest element zapewniania współbieżności i kontroli spójności danych. Pierwsze z brzegu: https://retool.com/blog/isolation-levels-and-locking-in-relational-databases/


”Engineering is easy. People are hard.” Bill Coughran
edytowany 3x, ostatnio: Charles_Ray
Bambo
Dzięki, jutro przeczytam i się odezwę, za dużo kodu na dziś
S9
@Bambo: i czemu swój post skasowałeś? :P
Bambo
Bo zrozumiałem jak jeszcze raz przeanalizowałem swój graf - w sensie, że błędnie założyłem, że T2 się zakomituje od razu jak pójdzie update. Tu był mój błąd. Odpisałem niżej.
S9
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 2 lata
  • Lokalizacja:Warszawa
  • Postów:1092
4

Dokumentacja Postgresa:

Read Committed is the default isolation level in PostgreSQL. When a transaction uses this isolation level, a SELECT query (without a FOR UPDATE/SHARE clause) sees only data committed before the query began; it never sees either uncommitted data or changes committed during query execution by concurrent transactions. In effect, a SELECT query sees a snapshot of the database as of the instant the query begins to run. However, SELECT does see the effects of previous updates executed within its own transaction, even though they are not yet committed. Also note that two successive SELECT commands can see different data, even though they are within a single transaction, if other transactions commit changes after the first SELECT starts and before the second SELECT starts.

UPDATE, DELETE, SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as SELECT in terms of searching for target rows: they will only find target rows that were committed as of the command start time.** However, such a target row might have already been updated (or deleted or locked) by another concurrent transaction by the time it is found. In this case, the would-be updater will wait for the first updating transaction to commit or roll back (if it is still in progress). **If the first updater rolls back, then its effects are negated and the second updater can proceed with updating the originally found row. If the first updater commits, the second updater will ignore the row if the first updater deleted it, otherwise it will attempt to apply its operation to the updated version of the row. The search condition of the command (the WHERE clause) is re-evaluated to see if the updated version of the row still matches the search condition. If so, the second updater proceeds with its operation using the updated version of the row. In the case of SELECT FOR UPDATE and SELECT FOR SHARE, this means it is the updated version of the row that is locked and returned to the client.


edytowany 1x, ostatnio: scibi_92
Bambo
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Postów:779
0

@ProgScibi:
Ok czyli wychodzi na to, że jak idzie fizycznie w DB update to nakładany jest lock i druga transakcja, żeby się zakommitować musi czekać, aż pierwsza się zakommituje albo zrollbackuje. Czaje, dzięki.
Wychodzi na to, że hibernate od razu zrobił sobie update do bazy, ale i tak czekał z zakkomitowaniem w T2 aż T1 się wykona. Gitara.

Skoq
Yup, w książce „designing data intensive applications” jest to dokładnie opisane w rozdziale o transakcjach
Bambo
Kaiazka do mnie doszła ostatnio, jade na urlop, wracam i zaczynam czytać;p
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)