Jakiś czas temu natknąłem się w pracy na problem obsługi złożonych transakcji.
Jako, iż SQL (a przynajmniej DBMS, z którego korzystamy) nie obsługuje zagnieżdżonych transakcji, do tej pory – dla wygody – wystarczała nam prosta modyfikacja adaptera bazodanowego, która pozwalała na “zagnieżdżanie transakcji”, a polegała jedynie na zliczaniu żądań rozpoczęcia transakcji i pilnowaniu liczby wywołań commitów/rollbacków na adapterze. W przypadku niezgodności rzeczywista transakcja (otwierana przy pierwszym żądaniu) nie była commitowana, podobnie, gdy wystąpiło przynajmniej jedno wywołanie rollback na adapterze – żadne zmiany nie były stosowane.
W rzadkich sytuacjach nietypowych, gdzie część zapytań miała zostać zastosowana, a część – w razie potrzeby – nie, obsługa odbywała się w kilku niezależnych transakcjach.
Jednak w końcu przyszedł ten moment, gdy rozbicie na wiele transakcji było praktycznie niemożliwe ze względu na złożoność procesu, który trzeba było zmodyfikować.
W skrajnym przypadku mogło to wyglądać tak:
- Początek procesu – wykonanie podstawowych zapytań INSERT/UPDATE
- Wykonanie serii zapytań INSERT/UPDATE z możliwością warunkowego ich cofnięcia.
- Zakończenie procesu – kolejne zapytania INSERT UPDATE.
W pewnych warunkach powinny być więc zastosowane zmiany z punku 1., cofnięte wszystkie z punktu drugiego i zastosowane zapytania z punktu trzeciego.
I tutaj przydatna okazała się klauzula SAVEPOINT, o której istnieniu wcześniej nie wiedziałem, natomiast idealnie wpasowywała się w tego typu sytuacje.
Jej zasada działania jest banalnie prosta. W momencie wywołania:
SAVEPOINT nazwa_savepointa
wewnątrz transakcji tworzony jest punkt, po którym następują kolejne zapytania i do którego można – w razie potrzeby – cofnąć się poprzez
ROLLBACK TO nazwa_savepointa
W ten sposób “zapominamy” o wykonanych po utworzeniu savepointa zapytaniach SQL, natomiast wszystkie zapytania sprzed tego czasu zostaną zastosowane, jeżeli na całej transakcji zostanie wykonany COMMIT.