Gdy nie uda się ROLLBACK

Że może się nie udać INSERT wie każdy, ot choćby zduplikowany klucz i klapa. Że COMMIT - też raczej każdy taką możliwość dopuszcza, choć nie zawsze są jasne okoliczności kiedy może się tak stać. A czy moze się nie udać ROLLBACK?

Dość długo byłem przekonany że owszem, może - ale tylko w sytuacjach typu 'utrata połączenia z bazą danych', gdy bieżąca transakcja i tak zostanie cofnięta a ten sam błąd będziemy już dostawać realizując dowolny SQL. Przekonanie to jest dosyć popularne, przynajmniej jeśli się popatrzy na ilość programów które - dokładnie testując statusy wykonania wszelkich SELECTów, INSERTów i COMMITów - realizują ROLLBACK nie sprawdzając statusu wykonania. Częste to szczególnie gdy dany ROLLBACK jest wykonywany w ramach obsługi błędów.

Poczułem się trochę zaskoczony, gdy kiedyś ROLLBACK nie powiódł mi się z błędem Unable to extend ROLLBACK SEGMENT. Ale prawdziwy szok przeżyłem, gdy okazało się że po nieudanym ROLLBACK może się udać COMMIT! Ot, pewien program wykonał serię modyfikacji w bazie danych, jako że ostatnia z nich się nie powiodła podjął próbę cofnięcia transakcji, zignorował wystąpienie błędu przy ROLLBACK a następnie po pewnym czasie wykonał COMMIT - i zatwierdził wszystkie zmiany, co do których cofnięcia był przekonany.

Tak kod dość łatwo napisać, starczy jeśli nasz program działa w jakiejś pętli, w której wnętrzu realizuje serię SQLi po czym COMMITuje lub cofa transakcję - i przechodzi do następnego obrotu pętli (czy też ponownie wyświetla jakieś okno, czy też pozwala zadać następną komendę, czy też przyjmuje następny request HTTP/IIOP/RPC/...). Jeśli ROLLBACK się nie powiedzie a program tego nie zauważy, poprzednia transakcja po prostu zostaje nie zakończona, następne operacje dzieją się w jej ramach (a nie jak się może wydawać, w ramach osobnej transakcji) i późniejszy COMMIT zatwierdza wszystko hurtem. Co zwykle wiąże się z zapisaniem w bazie niespójnych danych i błędnym poinformowaniem użytkownika o wynikach przetwarzania.

Jak sobie radzić? Ano, nie jest łatwo. Po pierwsze robiąc ROLLBACK trzeba sprawdzić, czy się to udało (sposób sprawdzania zależy od języka programowania i stosowanego API). Po drugie, sprawdziwszy, trzeba ewentualny błąd obsłużyć. Po trzecie, trzeba wymyślić sensowny sposób obsługi.

To ostatnie nie jest trywialne. W programach bazodanowych zwykle obsługujemy błędy informując użytkownika o problemie i cofając transakcję. A tu się nie udało cofnięcie transakcji... Cóż nam zostaje? Niestety, chyba tylko przerwanie działania programu (albo zerwanie połączenia bazodanowego i otwarcie nowego, niezależnego). Po zerwaniu połączenia / zakończeniu programu Oracle powinno ostatecznie cofnąć bieżącą transakcję (choć niekoniecznie od razu). Przed sięgnięciem po ten ostateczny krok można ewentualnie odczekać chwilę i podjąć ponowną próbę cofnięcia transakcji (ot, przykładowo podany problem z brakiem miejsca w rollback segmencie może zniknąć jeśli w międzyczasie zakończy się jakaś inna transakcja). W każdym razie trzeba ogromnie uważać, by przypadkiem nie przejść do dalszego działania co mogłoby pozwolić na wykonanie w ramach tego połączenia z bazą danych COMMIT.

komentarze obsługiwane przez Disqus