I.
Wprowadzenie do  języków zapytań (1)
II.
Wprowadzenie do  języków zapytań (2)
III.
Pojęcia obiektowości w bazach danych (1)
IV.
Pojęcia obiektowości w bazach danych (2)
  Wstęp
  1. Klasy
  2. Polimorfizm
  3. Role
  4. Kolekcje
  5. Trwałość
  6. Moduły
  Podsumowanie
  Zadania
V.
Podstawy semantyczne języków zapytań
VI.
Modele składu obiektów
VII.
Stos środowisk, rezultaty zapytań, funkcja nested
VIII.
Język SBQL (Stack-Based Query Language) (1)
IX.
X.
Dalsze własności SBQL
XI.
Operatory order by i group by
XII.
Przetwarzanie struktur nieregularnych
XIII.
Rozszerzenie języków zapytań o konstrukcje imperatywne
XIV.
Procedury, procedury funkcyjne, metody, reguły zakresu
XV.
Parametry procedur i metod, procedury rekurencyjne, optymalizacja poprzez modyfikację zapytań

 

5. Trwałość

Byt programistyczny jest trwały, jeżeli żyje dłużej niż trwa czas działania programu, który go używa (przenosi się pomiędzy kolejnymi uruchomieniami programu). Wszystko, co zawierają bazy danych, jest trwałe.

Trwałą zmienną jest zmienna programistyczna, która ma wszystkie własności normalnej zmiennej (w sensie konstrukcji programistycznych, w których może być użyta), ale której wartość przy nowym uruchomieniu programu jest taka sama, jak przy zakończeniu poprzedniego uruchomienia programu.

Popularne języki programowania (C, C++, Smalltalk, Pascal, Java,...) nie mają trwałych zmiennych. Wymagają wczytania explicite trwałej wartości z pliku zewnętrznego na swoją zmienną (i zapisania vice versa). Istnieje grupa prototypowych języków (DBPL, Napier88, PS-Algol, Galileo, Fibonacci, Tycoon i inne) posiadających trwałe zmienne. Ostatnio do tych języków dołączył również język Pjama, prototypowa wersja języka Java z trwałymi obiektami.

Analogicznie, obiekt jest trwały, jeżeli posiada własność trwałej zmiennej. Dość często trwały obiekt jest utożsamiany z obiektem bazy danych, chociaż bazy danych wymagają wielu innych cech (takich jak np. współbieżny dostęp do obiektów), których nie posiada czysta koncepcja trwałych obiektów. Popularne języki obiektowe (Smalltalk, C++, Java, Eiffel) również nie mają trwałych obiektów. Ponadto, mają ograniczenia w zakresie typów masowych (bulk), które są inną podstawową własnością baz danych. Z tych względów adekwatność tych języków do programowania aplikacji z bazą danych może być kwestionowana. Istnieją opinie, że jeżeli posiadałyby one trwałe obiekty, typy masowe i pełną integrację z językiem zapytań, wówczas kod aplikacji pisany w tych językach zredukowałby się o co najmniej 70%. Mimo ogromnego (i nieco przesadnego) nagłośnienia zalet języka Java, w tym względzie jest on językiem dość tradycyjnym i nie powoduje istotnej zmiany jakości technik budowy oprogramowania.

Trwałe zmienne lub obiekty implikują trwałość ich klas i/lub typów. Jeżeli pewien trwały obiekt jest dostępny wielu programom, to informacja o jego budowie, reprezentacji wartości przechowywanych wewnątrz tego obiektu, metody działające na obiekcie itd. nie mogą być własnością programu, który ten obiekt utworzył. Są one niezbędnie dla innych programów wykorzystujących ten obiekt, również wtedy, kiedy program tworzący obiekt przestaje być aktywny lub przestaje istnieć. Istnienie samego obiektu bez tych informacji jest najczęściej pozbawione sensu.

Alternatywą dla trwałości klasy i/lub typu obiektu jest powtarzanie jej/jego definicji wewnątrz każdego programu, który będzie odwoływał się do trwałego obiektu, lub istnienie specjalnego modułu źródłowego przechowującego te informacje. Moduł ten musi być konsolidowany z każdym programem używającym trwałej zmiennej. Takie rozwiązanie zostało zastosowane w DBPL.

Jeżeli ponadto przyjmiemy, że ten sam trwały obiekt powinien być dostępny z wielu języków programowania (co w bazach danych jest regułą), wówczas powstają nietrywialne problemy takiej reprezentacji jego typu lub klasy, która byłaby kompatybilna z wszystkimi językami, które mogą tego obiektu używać. Zwróćmy uwagę, że w wielu językach, np. w C++, klasa jest pojęciem drugiej kategorii programistycznej i (praktycznie) istnieje wyłącznie w tekście programu, który nie może być przeszukiwany lub odpytywany przez inny program. Skutkiem tego jest fakt, że w bazach danych przechowywane są wyłącznie wartości składające się na obiekt (czyli jego stan), zaś wszelkie pozostałe informacje, m.in. metody działające na obiekcie, są przechowywane ad hoc, w ramach dynamicznych bibliotek, wykonywalnych plików programów itp. Dość poważnym problemem jest także przesyłanie obiektów w sieci komputerowej. Ponieważ taki obiekt nie może być poprawnie wykorzystany bez znajomości jego typu i klasy (oraz bez fizycznej obecności metod znajdujących się w klasie), przesłanie obiektu może wymagać przesłania lub dublowania informacji o jego typie i klasie.

Powyższe obserwacje mogą stanowić wyjaśnienie przyczyny często wznawianych dyskusji nad sensem wprowadzania trwałych obiektów do języków programowania lub nad zasadami organizacji obiektowych baz danych. Istnieją propozycje wydzielenia typów/klas obiektów z języków programowania baz danych i uczynienia z nich warstwy wspólnej, dostępnej dla wielu języków. Jak dotąd, te propozycje nie doczekały się efektów w konstrukcji nowo powstających języków i systemów.


5.1 Trwałość poprzez osiągalność

Trwałość poprzez osiągalność (persistence through reachability) jest ograniczeniem na struktury danych, którego intencją jest przeciwdziałanie potencjalnym niespójnościom bazy danych. Obiekt trwały nie może mieć nietrwałych podobiektów lub atrybutów, ani też nie może zawierać pointera prowadzącego do obiektu nietrwałego. Wszystkie obiekty trwałe i ich atrybuty są osiągalne z jednego "trwałego korzenia". Obiekty nietrwałe mogą mieć pointery prowadzące do trwałych obiektów, ale nie mogą mieć trwałych atrybutów. Dzięki tej zasadzie awaria lub przerwanie dowolnego programu działającego na bazie danych (tworzącego nietrwałe obiekty) nie spowoduje katastrofalnych skutków dla spójności bazy danych, gdyż nie może doprowadzić do chwilowego stanu, w którym obiekty trwałe byłyby przemieszane z obiektami nietrwałymi.

Trwałość poprzez osiągalność implikuje (zdaniem niektórych autorów) brak w języku operacji usuwania obiektu oraz obecność automatycznego mechanizmu zbierania nieużytków (garbage collector), który automatycznie usunie obiekt, gdy stanie się on niedostępny z powodu braku pointerów lub referencji, które do niego prowadzą. Zdaniem zwolenników tej koncepcji, implikuje to następujące pozytywne aspekty:

  • Brak możliwości powstawania niespójności z powodu zwisających pointerów (dangling pointers).
  • Uproszczenie koncepcji dostępu do trwałych obiektów, mechanizmów zarządzania transakcjami, odtwarzania itd.


Nie jest jednak do końca jasne, dlaczego takie właśnie ograniczenie organizacji obiektowych baz danych ma mieć tego rodzaju pozytywne konsekwencje. Argumenty na rzecz trwałości poprzez osiągalność są jak dotąd wyłącznie pewną retoryką (lub raczej ideologią), która nie jest przekonywująca. Nie istnieją jednoznacznie obiektywne testy lub implementacje wskazujące na to, że złamanie tej zasady prowadzi do negatywnych skutków. W szczególności, w systemie Loqis zrealizowanym przez autora nie zastosowano się do tej zasady i nie widać istotnych powodów, dla których miałoby to być niekorzystne.

Wydaje się, że obrońcy zasady trwałości poprzez osiągalność zbytnio przywiązuje swoje myślenie do poziomu fizycznej implementacji struktur danych, który (jak to stwierdzono przez dziesięciolecia) może być całkowicie ukryty przed użytkownikiem i programistą. W szczególności, do takich "cudownych środków" niskiego poziomu należy metoda unikania niespójności z powodu zwisających pointerów poprzez zabronienie operacji usuwania explicite i oddelegowania tej operacji do mechanizmu zbierania nieużytków (metoda ta stała się pewnego rodzaju podstawą ideologiczną trwałości poprzez osiągalność). Łatwo sobie wyobrazić, że w ten sposób nie usuwa się źródła potencjalnych błędów, lecz "maskuje się" je w taki sposób, że skutki potencjalnych błędów objawią się później, wskutek czego mogą mieć bardziej dramatyczne i trudniej wykrywalne konsekwencje. Jeżeli programista musi usunąć obiekt (ponieważ np. dane konto bankowe przestało istnieć), ale nie dysponuje operacją usuwania obiektu, wówczas musi zadbać o to, aby usunąć/wyzerować wszystkie odwołania do tego obiektu. Jeżeli zapomni o którymkolwiek lub z powodu hermetyzacji, praw dostępu lub zakomunikowanego mu podschematu bazy danych nie będzie mógł wyzerować wszystkich odwołań, wówczas ten obiekt będzie nadal istnieć, będąc potencjalnym źródłem niespójności. Np. operacja "podaj średni stan wszystkich kont", która odwoła się do nie do końca usuniętego obiektu "konto", spowoduje błędny wynik. Błąd ten będzie praktycznie niewykrywalny, zaś w procesie kontaminacji jego skutki mogą rozprzestrzenić się w niekontrolowany sposób na środowisko bazy danych, procesy przetwarzania i środowisko zewnętrzne systemu informatycznego. W tej sytuacji znacznie lepszy mógłby być natychmiastowy błąd spowodowany przez zwisający pointer. Istnieją metody unikania zwisających pointerów mimo istnienia operacji usuwania obiektu explicite; jedną z nich zastosowano w systemie Loqis.

Zasada trwałości poprzez osiągalność pozostaje w pewnej sprzeczności z inną, znacznie ważniejszą zasadą ortogonalnej trwałości, która będzie omówiona w jednym z następnych podrozdziałów. Ograniczenia trwałości poprzez osiągalność można łatwo uniknąć przyjmując, że każda dana oraz pointer do danej są dodatkowo adnotowane flagą trwałości. Przy takim założeniu możliwe jest np. wstawianie nietrwałych atrybutów do trwałych obiektów, co w systemie Loqis okazało się dość wygodną techniką programistyczną.

Zasada trwałości poprzez osiągalność jest zrealizowana w standardzie ODMG w wiązaniach do języków Smalltalk i Java. W wiązaniu do C++ zrezygnowano z tej zasady z powodu braku mechanizmu automatycznego zbierania nieużytków.


5.2. Trwałość poprzez dziedziczenie

 Trwałość poprzez dziedziczenie (persistence through inheritance) jest nieco innym ograniczeniem organizacji obiektowej bazy danych. Zakłada ono, że cecha trwałości jest cechą definiowanej klasy oraz wszystkich jej podklas. Oznacza to, że żadna klasa nie może zawierać bezpośrednio zarówno obiektów trwałych, jak i nietrwałych. Możliwe jest utworzenie trwałej klasy, która jest specjalizacją klasy nietrwałej, ale nie odwrotnie.

Wadą tej koncepcji jest znaczne ograniczenie ortogonalności języka lub systemu. W szczególności, znacznie są ograniczone możliwości definiowania metod działających identycznie na trwałych i ulotnych danych, co jest sprzeczne z zasadą ortogonalnej trwałości.

Zaletą zasady trwałości poprzez dziedziczenie jest wyraźny podział funkcjonalności systemu na część zajmującą się bazą danych i część zajmującą się nietrwałymi obiektami wewnątrz przestrzeni adresowej programu. Dla istniejących języków programowania ten podział jest istotny, gdyż jest usankcjonowany faktem całkowicie różnych środków dostępu (wiązania) do danych znajdujących się w pamięci operacyjnej i do danych przechowywanych na dysku. Zasada ta prowadzi jednak do istotnych (i niepotrzebnych) ograniczeń dla interfejsów programistycznych operujących na wyższym poziomie abstrakcji, np. dla języków zapytań, które miałyby spójnie łączyć w ramach jednorodnej składni dostęp do trwałych i nietrwałych obiektów. Mimo że zasada ta nie jest zbytnio nagłośniona w obiektowej literaturze, wiele koncepcji systemów przyjmuje ją mniej lub bardziej implicite.


5.3. Ortogonalna trwałość

Tradycyjnie bazy danych przechowywały wyłącznie typy trwałe i masowe (zbiory, relacje, etc.). Podobnie, klasyczne języki programowania zajmowały się wyłącznie typami indywidualnymi i nietrwałymi (zmienne, struktury, zapisy, etc.). Co więcej, wskutek różnych linii rozwoju baz danych i języków programowania wykształciły się różnice w koncepcjach dostępu do bazy danych i dostępu do zmiennych programu.

W istocie, nie istnieje logiczne uzasadnienie takiego podziału. Można podać wiele przykładów, kiedy niezbędne jest zapamiętanie w bazie danych nie tylko danych masowych, ale i pojedynczych wartości; np. adresu firmy, w której jest zainstalowany system. Podobnie, brak typów masowych w językach programowania doprowadził do koncepcji "sterty" (heap), która ma liczne wady, w szczególności ograniczoną kontrolę typów, konieczność dynamicznych operacji alokacji i zwalniania pamięci, konieczność przetwarzania poprzez pointery.

Ortogonalna trwałość (orthogonal persistence) oznacza własność języka programowania, w którym cecha trwałości jest ortogonalna w stosunku do konstruktorów typu.

W szczególności, baza danych może przechowywać dane indywidualne (trwałe), zaś w obszarze roboczym programu mogą znajdować się dane masowe (nietrwałe). Cecha trwałości powinna być obsługiwana przez wyspecjalizowane funkcje, ale wszystkie pozostałe funkcjonalności (w tym języki zapytań) nie powinny robić żadnej różnicy w środkach (składni i semantyce) dostępu do trwałych i nietrwałych danych. Ortogonalna trwałość jest cechą takich języków, jak PS-Algol, DBPL, Napier88, Galileo. Jak wspomnieliśmy, popularne języki obiektowe Smalltalk i C++ nie mają w ogóle zmiennych trwałych i mają dość silnie ograniczone typy masowe, wobec czego nie można tam mówić o ortogonalnej trwałości. Ortogonalna trwałość jest natomiast wprowadzona do wersji języka Java (znanych jako Persistent Java i PJama).

Standard ODMG nie zakłada ortogonalnej trwałości, co jest przedmiotem krytyki. Z drugiej strony, dość często przedstawiciele świata komercyjnego krytykują koncepcję ortogonalnej trwałości jako niepraktyczną i w istocie niepotrzebną. Powód tych głosów jest jasny: wiele firm zajmujących się obiektowymi bazami danych rozwija obecnie interfejsy oparte na językach obiektowych, takich jak Java, dla których cecha ortogonalnej trwałości nie jest łatwa do wprowadzenia.

Copyrights © 2006 PJWSTK
Materiały zostały opracowane w PJWSTK w projekcie współfinansowanym ze środków EFS.