Select for update i transakcje

0

Cześć, mam taki case, gdzie upraszczając problem do minimum posiadam tabelę z kolumnami id oraz counter. Muszę uruchamiać pewną logikę, a następnie inkrementować licznik. Równoległa transakcja, która również chce zrobić to samo powinna poczekać, aż ta 1 transakcja zrobi swoją logikę i zupdatuję licznik. Nałożyłem zatem na metodę findById() locka PESSIMISTIC_WRITE i po jej wywołaniu mam locka na wierszu z licznikiem i ta druga transakcja czeka. Pojawiają się jednak pewien problem:

Pierwsza transakcja sprawdza sobie, że jeszcze nie istnieje row z takim id, robi saveAndFlush(), następnie robi select for update, czyli findById(), robi logikę, inkrementuje licznik i wywołuje save(). Jeżeli w momencie wykonywania dłuższej logiki przez 1 transakcję wywołam drugą transakcję to metoda existById powie, że obiektu nie ma, ale saveAndFlush już rzuca wyjątek, bo istenieje taki obiekt.

Nie mam pomysłu jak to naprawić. Może kwestia propagacji transakcji ?

    @Transactional
    public void foo() {

        final String id = "ID";

        if (!fooRepository.existsById(id)) {
            fooRepository.saveAndFlush(new Foo(id, 0));
        }

        fooRepository.findById(id) // findById ma locka PESSIMISTIC_WRITE
                .ifPresent(this::someLogic);
    }

    private void someLogic(final Foo foo) {
        try {
            Thread.sleep(5000); // imitacja jakiejś dłuższej logiki
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        foo.increment();
        fooRepository.save(foo);
    }
2

Wydaje mi się, że masz wyścig w pierwszym ifie - 2 transakcje/wątki mogą nie znaleźć wiersza i próbować zapisać to samo id. Więc tak naprawdę nie dochodzą nawet do select for update. Odnośnie tego sfu, to sprawdziłbym jak to działa na konkretnej bazie danych przy określonym stopniu izolacji transakcji (nie propagacji!).

0

Tak na szybko bo jadę autem - 1 transakcja dochodzi do sfu i wchodzi w sleepa. 2 się wywala jak 1 sleep się skonczy i ta 2 dojdzie do saveAmdFlush

2

W ogóle to napisz co chcesz osiągnąć, bo może da się to łatwiej zrobić. Już widzę jak ten kod za 3 miesiące przestanie spełnić założenia, bo i test ciężko na to napisać (trzeba by wstrzyknąć jakieś CountDownLatche itd).

0

Edit, zadziałało z izolacją READ_UNCOMMITED.

3

Ło panie- uważaj z READ_UNCOMMITED - to w zasadzie nie powinno być używane...

  • kiedyś jedna duża niemiecka firma miała niezy problem przez happy READ_UNCOMMITED wstawione przez programistę, bo mu coś nie działało (działało wolno), groziły potężne kary od skarbówki i klientów - czy wiesz co robisz?

EDIT:
a co do problemu
załóż obiekt w osobnej transakcji, albo nawet w skrypcie inicjalizującym db

chyba chcesz stworzyć coś w stylu cluster locka?
generalnie bazy danych się do takiego blokowania średnio nadają - to jebnie - najpóźniej jak ktoś zacznie się bawić w replikację db.

Zookeper jest stworzony do takich rzeczy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1