Ten przeklęty nie blokujący INSERT

Sprawa, która moim i nie tylko moim zdaniem jest jednak błędem Oracle (support Oracle niestety tej opinii nie podziela).

Oracle, realizując polecenie INSERT, nie nakłada blokady na wstawiany rekord!

Innymi słowy, wyobraźmy sobie, że pewien proces zrealizował INSERT ale jeszcze nie zatwierdził transakcji. Inny proces robi SELECT FOR UPDATE, UPDATE albo DELETE - zadając klucz zgodny z kluczem nowo wstawionego rekordu - i nie zostaje w żaden sposób zablokowany! Po prostu gładko omija nowo wstawiony rekord - i tyle.

W zależności od sytuacji może być to mniej lub bardziej kłopotliwe. Na pewno sprawia bardzo grube problemy, gdy ważne jest dla nas wymuszane blokadami sortowanie transakcji na bazie danych (częste, gdy implementujemy jakiś mechanizm odgrywania transakcji na zapasowej bazie danych - patrz artykuł przedstawiający sytuację, w której zetknąłem się z tym problemem).

Problem nie znika nawet po ustawieniu trybu pracy SERIALIZABLE (choć nie jest trudno skonstruować przykład, w którym dzięki opisanej cesze Oracle przetworzy dwie transakcje w sposób nie odpowiadający żadnemu wykonaniu seryjnemu).

Jeszcze może przykład kodu, który często jest pisany - a który jest proszeniem się o problemy (kod ten nie jest w pełni dobry nawet gdyby Oracle nakładał przy INSERT blokadę - ale wtedy szansa na kłopoty byłaby znacznie mniejsza). Chodzi o logikę typu:

  • sprawdzamy, czy rekord o danym kluczu istnieje (realizując SELECT FOR UPDATE);
  • jeśli tak, modyfikujemy go (UPDATE);
  • jeśli nie, wstawiamy nowy rekord (robiąc INSERT).

Powiedzmy, że proces A wykonał już tego typu sekwencję (dodał nowy rekord) ale jeszcze nie scommitował. Proces B robi SELECT FOR UPDATE - jak napisałem, nie zostaje zablokowany tylko nic nie znajduje - więc też robi INSERT. I mamy dwa rekordy (albo błąd duplikacji klucza).

O ile nasza tablica posiada klucz główny, prawidłowa jest inna sekwencja:

  • na ślepo próbujemy zrobić INSERT;
  • dopiero jeśli INSERT się nie udał (błąd duplikacji klucza) robimy UPDATE (albo SELECT FOR UPDATE).

W tym wypadku, jeśli dwa procesy próbują realizować powyższą sekwencję dla tego samego klucza, Oracle prawidłowo zablokuje drugi proces - blokując ... dostęp do odpowiedniego bloku w indeksie. Przy UPDATE pól niekluczowych pomaga - z tego samego powodu - założenie na nich indeksu.

Ale przed sytuacją, w której jeszcze nie zatwierdzony INSERT podpada pod jakieś globalne UPDATE, nie możemy się zabezpieczyć w żaden sposób (inny niż odpowiednia logika aplikacyjna).

Trochę więcej (i już mocno technicznie) o sytuacji, w której natknąłem się na problem, można na osobnej stronie.

komentarze obsługiwane przez Disqus