84

Sdj Extra 35 Db2

Embed Size (px)

DESCRIPTION

Software Developers Jurnal

Citation preview

Page 1: Sdj Extra 35 Db2
Page 2: Sdj Extra 35 Db2
Page 3: Sdj Extra 35 Db2

�www.sdjournal.org

SDJ Extra 35

SpiS treści 40 <XML> w praktyceMarcin MolakW ramach artykułu przedstawione zostają przykładowe zastoso-wania dokumentów XML i ich pełna obsługa w bazie danych iBM DB2. Kolejne przykłady wprowadzają czytelnika w świat technolo-gii iBM pureXML.

50 Poziomy izolacji w DB2Artur Wroński, Krzysztof MikołajewskiW ramach artykułu autorzy poruszają tematykę właściwego wy-korzystania poziomów izolacji celem zapewnienia maksymal-nej współbieżności aplikacji. przedstawiona jest również koncep-cja optymistycznego blokowania, coraz bardziej popularna wśród programistów.

ADMinistrAcjA 60 Backup od A do ZArtur WrońskiW artykule autor opisuje dostępne w DB2 techniki tworzenia kopii zapasowych. Szczegółowo omawia backup online i offline. porusza także bardziej zaawansowane zagadnienia, takie jak odtwarzanie obszarów tabel przy pracującej bazie danych, czy backup bardzo dużych baz danych.

70 Mechanizm zarządzania obciążeniem w DB2Rafał Stryjek, Przemysław KantykaAutorzy prezentują poszczególne etapy związane z uruchamia-niem menadżera obciążeń w iBM DB2.

78 Kompresja w DB2Artur WrońskiAutor na przykładzie przedstawia mechanizmy kompresji danych w iBM DB2 9.7, m.in mechanizm kompresji tabel, indeksów, bac-kup-ów i dokumentów XML

PrZegLąD funKcjonALności4 nowości iBM DB2 9.7Artur Wroński, Marcin MolakArtykuł wprowadza w nowe funkcjonalności, które pojawiły się w wersji iBM DB2 9.7.

integrAcjA DAnych8 integracja DB2 z bazami danych oracle Paweł Drzymała, Henryk WelfleMechanizm federacji w iBM DB2 pozwala na wirtualny dostęp do danych zarządzanych przez serwery baz danych różnych produ-centów. Artykuł przedstawia kolejne kroki w konfiguracji środo-wiska, które pozwalają na zintegrowanie bazy danych iBM DB2 z Oracle DB.

tworZenie APLiKAcji14 Procedury składowane w DB2 9Dariusz DeptaArtykuł przybliża tworzenie procedur składowanych w bazach iBM DB2. Autor koncentruje się nie tylko na podstawowych konstruk-cjach języka SQL pL, ale porusza również tematy zaawansowane, takie jak wykonywanie kodu dynamicznego czy obsługa błędów.

34 DB2 coBrA – converting oracle Becomes re-ally AffordableMarcin Molak

Autor opisuje nowe funkcjonalności, wprowadzone w wersjach iBM DB2 9.5 i 9.7, ułatwiające proces migracji aplikacji opartych na serwerze Oracle DB.

Miesięcznik Software Developer’s Journal (12 numerów w roku)jest wydawany przez Software Press Sp. z o.o. SK

Dyrektor wydawniczy: Anna Adamczyk

Redaktor naczelny: Łukasz Łopuszański [email protected] merytoryczny i opracowanie CD: Artur Wroński [email protected] Marcin Molak [email protected] prowadzący: Tomasz Łopuszański [email protected]: Tomasz Łopuszański [email protected]

Projekt okładki: Agnieszka MarchockaSkład i łamanie: Przemysław Banasiewicz, Ireneusz Pogroszewski

Dział produkcji i kolportażu: Alina Stebakow [email protected]ład: 6 000 egz.

Adres korespondencyjny:Software Press Sp. z o.o. SK, ul. Bokserska 1, 02-682 Warszawa, Polskatel. +48 22 427 36 91, fax +48 22 224 24 59www.sdjournal.org [email protected]

Dział reklamy: [email protected]ługa prenumeraty: EuroPress Polska [email protected]

Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain.

Uszkodzone podczas wysyłki płyty wymienia redakcja.

Wszystkie znaki firmowe zawarte w piśmie są własnością odpowiednich firm.Zostały użyte wyłącznie w celach informacyjnych.

Redakcja używa systemu automatycznego składu

Osoby zainteresowane współpracą prosimy o kontakt:[email protected]

Druk: Artdruk www.artdruk.com

Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu programów zamieszczonych na płycie CD-ROM dostarczonej razem z pismem.

Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.

Page 4: Sdj Extra 35 Db2

03/2009 (5)�

03/2009 (5)

Kompresja indeksów i tabel tymczasowychMechanizmy kompresji tabel wprowadzone w wersji 9.1 zawsze były bardzo mocną stroną DB2. W ciągu ostatniego roku ponad 100 klien-tów SAP na świecie zdecydowało się zmigrować bazę na DB2 głów-nie ze względu na tę funkcjonalność. Mniejsza baza jest szybszą ba-zą, szybciej wykonuje się backup, a mniej zajętego miejsca na dysku oznacza mniejsze koszty. W wersji 9.5 wprowadzono mechanizmy automatycznej kompresji pozwalające na kompresję ładowanych da-nych bez konieczności reorganizacji danych. W 9.7 kompresję tabel uzupełniono o kompresję indeksów, obiektów BLOB i dokumentów XML oraz o automatyczną kompresję obiektów tymczasowych. Zmie-niono także format zapisu do dziennika transakcji, tak by informacja o operacjach na skompresowanych obiektach mogła być wykorzystana przez mechanizmy replikacji. Więcej na temat kompresji dowiesz się w artykule Kompresja w DB2.

Język PL/SQLZalety procedur składowanych i funkcji zdefiniowanych przez użyt-kownika sprawiają, że wiele działających dziś systemów opiera część logiki aplikacji na kodzie proceduralnym baz danych. Jednak brak jednolitego standardu sprawił, że producenci serwerów danych roz-wijali przez lata własne implementacje języków. Podczas gdy środo-wiska deweloperskie związane z bazą DB2 mogły korzystać z języ-ka SQL PL, programiści serwera danych Oracle DB opierali swój kod na PL/SQL. Konieczność przepisywania linii kodu do nowego dialek-tu stawała się często długotrwałym elementem procesów migracyj-nych. Mając to na uwadze, w ramach DB2 9.7 udostępniono progra-mistom Oracle DB obsługę języka PL/SQL. Należy podkreślić, iż kod stworzony w tym języku nie jest konwertowany do języka SQL PL. Jest on bowiem traktowany w sposób równorzędny i bezpośrednio kompilowany do kodu wykonawczego DB2. Wraz z obsługą składni DB2 zapewnia również możliwość debugowania i profilowania kodu PL/SQL. A dzięki udostępnieniu nowych typów danych oraz modu-łów procedur składowanych i funkcji (np. DBMS_OUTPUT) przepisywa-nie kodu staje się wyjątkiem, a nie regułą procesu migracyjnego.

Udoskonalony menadżer obciążeniaWiększość dostępnych na rynku baz danych równomiernie przydziela zasoby poszczególnym sesjom aplikacji. W sytuacji, w której na jednej

bazie danych pracuje wielu użytkowników o różnej charakterystyce przetwarzania (np. raportowanie miesięczne, krótkie raporty opera-cyjne, operacje online), równomierny przydział zasobów może nie od-zwierciedlać priorytetów biznesowych. Udoskonalony w wersji DB2 9.7 menadżer obciążenia (ang. workload manager) pozwala w łatwy sposób kontrolować moc maszyny przydzielaną poszczególnym zada-niom. W DB2 9.7 wprowadzono koncepcję starzenia się przetwarza-nia. Wykonywana instrukcja SQL (bądź sesja) po przekroczeniu okre-ślonych progów (np. czasu wykorzystanego procesora) może być prze-niesiona do innej klasy przetwarzania, o zupełnie innych priorytetach wykorzystania zasobów sprzętowych. Dzięki takiemu podejściu baza danych może automatycznie zlokalizować bardzo obciążające zapyta-nia i podjąć stosowne akcje (np. zmniejszyć priorytet bądź zakończyć zadanie), tak by zagwarantować określoną wydajność dla najważniej-szych zadań. W DB2 9.7 można śledzić ilość czytanych wierszy, czas wykorzystanego procesora, łączny czas przetwarzania czy miejsce wy-korzystane w obszarze tymczasowym. Menadżer obciążenia DB2 mo-że na starcie traktować wszystkie sesje jako równoważne i podejmo-wać określone akcje w wyniku śledzenia aktywności. Istnieje także możliwość podejmowania określonych akcji jeszcze przed urucho-mieniem zapytania. DB2 może wyznaczyć koszt danej operacji i za-leżnie od kosztu umieścić zadanie w określonej klasie przetwarzania bądź zabronić wykonania danej operacji. Taka funkcjonalność może być szczególnie przydatna w dużych systemach hurtowni danych, w których użytkownicy końcowi mogą wygenerować zapytania dopro-wadzające do zatkania się systemu (np. złączenie dwóch terabajto-wych tabel bez warunku ograniczającego). Mechanizmy zarządzania obciążeniem w DB2 pozwalają wprowadzić pełną kontrolę nad zacho-waniem się bazy w takich sytuacjach. Menadżer obciążenia jest wbu-dowany w jądro DB2 9.7 i jest zintegrowany menadżerem obciążenia systemu operacyjnego Linux oraz AIX. Więcej na temat tego mecha-nizmu dowiesz się w artykule Mechanizm zarządzania obciążeniem w DB2

Inlining obiektów BLOB i XMLDuże obiekty, takie jak dane typu BLOB, CLOB, w DB2 standardowo przechowywane są poza stronami z danymi. W ich obrębie przechowy-wany jest jedynie znacznik wskazujący na faktyczne położenie obiek-tu. Takie podejście jest optymalne do dużych obiektów. Czasami jed-

Nowości IBM DB2 9.7Dla projektantów wersji DB2 9.7 głównym priorytetem było obniżenie kosztów użytkowania bazy danych, zapewnienie maksymalnego bezpieczeństwa i niezawodności oraz maksymalne ułatwienie wykonywanych czynności administracyjnych. Wprowadzając nowe funkcjonalności, pamiętali również o programistach, tworzących aplikacje bazodanowe. W artykule zestawiliśmy wybrane funkcjonalności, które pojawiły się w wersji 9.7.

Artur Wroński, Marcin Molak

Page 5: Sdj Extra 35 Db2
Page 6: Sdj Extra 35 Db2

03/2009 (5)�

03/2009 (5)

nak w praktyce do pól typu BLOB, CLOB wstawiane są dane nieznacz-nych rozmiarów i wtedy dużo rozsądniej umieścić je razem z pozostały-mi danymi. Taka metoda przechowywania obiektów określana jest jako inline-ing. Wersja 9.7 pozwala określić próg inline-ingu. Obiekty mniej-sze od założonego rozmiaru będą przechowywane na stronach razem z danymi. Po przekroczeniu zadeklarowanego progu (np. w wyniku aktu-alizacji danych) obiekt jest automatycznie przenoszony do dedykowa-nego obszaru tabel. Takie podejście można stosować nie tylko w przy-padku obiektów typu BLOB, CLOB, ale także dokumentów XML. Inli-ne-ing pozwala na znaczne przyspieszenie aplikacji w przypadku dużych obiektów o małym rozmiarze. Pozwala także skorzystać z mechanizmów kompresji w odniesieniu do BLOB-ów i dokumentów XML.

Koncentrator wyrażeń Bardzo dobrym nawykiem programowania jest wykorzystywanie w zapytaniach SQL tzw. znaczników parametrów (ang. parameter mar-ker). By wstawić określoną liczbę rekordów, dużo lepiej jest przygoto-wać jedną generyczną instrukcję, w której zamiast wartości podaje się znak pytajnika. Instrukcję taką należy skompilować (PREPARE) i do-piero podczas wykonywania podawać określone wartości (EXECUTE ... USING). Takie podejście bardzo korzystnie wpływa na wydajność, ponieważ wielokrotne wykonywanie instrukcji z różnymi wartościa-mi wymaga tylko jednokrotnej kompilacji zapytania. We wcześniej-szych wersjach DB2 korzystanie z tego mechanizmu wymagało odpo-wiedniego napisania aplikacji. DB2 9.7 automatycznie rozpoznaje po-dobne do siebie instrukcje SQL i wewnętrznie kompiluje jawnie po-dane wartości do postaci z wykorzystaniem znaczników parametrów (ang. statement concentrator).

Odczyt bez blokadDB2 9.7 wprowadza nowy poziom izolacji Currently Committed, któ-ry powinien znacznie ułatwić przenoszenie aplikacji pracujących z ba-zą Oracle. Zapytania wykonane w tym poziomie izolacji nie są wstrzy-mywane na blokadach, nawet w sytuacji, gdy inna sesja przetwarza da-ny rekord na wyłączność. Jeśli rekord został zmodyfikowany, ale jesz-cze nie zatwierdzono zmian, wtedy zapytanie wykonujące się w po-ziomie izolacji Currently Committed automatycznie sięgnie do dzien-nika transakcji, by pobrać zatwierdzoną wartość sprzed modyfikacji. Poziom Currently Committed nie wymaga jawnej deklaracji i jest do-myślnie realizowany dla wszystkich zapytań Cursor Stability wykony-wanych z intencją odczytu. Więcej na temat współbieżności dostępu do danych przedstawiliśmy w artykule Poziomy izolacji w DB2.

Proste odzyskiwanie przestrzeniW DB2 9.7 zmieniono wewnętrzny format obszarów tabel, tak by umożliwić łatwe odzyskiwanie miejsca na dysku. Jeśli na skutek reor-ganizacji danych, kompresji istniejących rekordów, bądź po prostu w wyniku usunięcia części danych w obszarze tabel zostało zwolnione miejsce, wtedy jednym poleceniem ALTER TABLESPACE REDUCE miejsce to może być zwrócone do systemu plików. W instrukcji zmie-niającej obszar tabel można podać określony rozmiar, o który mają być zmniejszone pliki bazy danych, ale można także wymusić maksymal-ne możliwe pomniejszenie. Zmniejszanie plików bazy danych może być wykonywane w trakcie pracy bazy danych (operacja online) i mo-że być realizowane zarówno na przestrzeniach Database Managed Spa-ce (DMS), jak i Automatic Storage. Nowy format obszarów tabel pozwa-la także na proste odzyskiwanie przestrzeni z pustych bloków MDC (Multidimesional Clustering) bez konieczności reorganizacji tabeli.

Przenoszenie tabel w trybie on-lineCzasami administratorzy stoją przed koniecznością eksportu danych do plików tekstowych oraz powtórnego załadowania ich do bazy. Taka operacja może być wymuszona w sytuacji, w której muszą być zmie-

nione określone własności obszarów tabel, np. rozmiar strony. W DB2 9.7 wprowadzone zostały specjalne procedury składowane umożli-wiające przeniesienie tabel do nowych obszarów, z gwarancją ciągłego dostępu użytkowników do danych (tryb online). Ta czynność admini-stracyjna została w pełni zautomatyzowana, odciążając administrato-rów od konieczności ręcznej migracji danych.

Przenoszalne przestrzenie tabelO ile przeniesienie jednej tabeli do nowej bazy nie jest zadaniem spe-cjalnie trudnym, o tyle przeniesienie całej przestrzeni tabel, obejmu-jącej tysiące obiektów, może być już operacją uciążliwą. Serwer DB2 zapewnia w tym zakresie narzędzia: db2look do generowania plików DDL ze strukturą obiektów oraz db2move do eksportowania danych do plików i ich ładowania do bazy. Wykorzystanie tych narzędzi wy-maga przygotowania nowych przestrzeni tabel, przypisania im odpo-wiednich kontenerów, a także kontrolowania procesów eksportu i im-portu danych. Wersja 9.7 przynosi w tym zakresie znaczące uspraw-nienie. Wbudowany mechanizm przenoszalnych przestrzeni tabel (ang. transportable tablespaces) pozwala na migracje całej przestrzeni tabel do nowej bazy i automatyczne dowiązanie jej kontenerów. Dzię-ki temu skraca się czas migracji i przygotowania nowych środowisk.

Zmiana metody zarządzania obszarem tabelPodczas tworzenia obszaru tabel administrator musi zdecydować się, czy obszar będzie zarządzany automatycznie przez bazę danych (Au-tomatic Storage), czy przez administratora (DMS). Obydwa rodzaje ob-szarów tabel dają możliwość automatycznego powiększania plików. Główną różnicą pomiędzy tymi obszarami tabel jest to, że w przy-padku Automatic Storage baza samoczynnie dobiera nazwy, położenie oraz wielkość plików. W obszarach typu DMS administrator ma pełną kontrolę nad tymi elementami. We wcześniejszych wersjach DB2 kon-wersja jednego obszaru w drugi wymagała utworzenia nowego obsza-ru tabel i powtórnego załadowania danymi. DB2 9.7 udostępnia me-chanizmy, które pozwolą taką konwersję wykonać w locie przy pracu-jącej bazie danych.

Narzędzia do zarządzania cyklem życia informacji – OptimWraz z wersją DB2 9.7 IBM wprowadził na rynek rodzinę narzędzi Optim do zarządzania cyklem życia informacji. IBM Optim Databa-se Administrator 2.2 jest środowiskiem, które pozwala z jednego miej-sca zarządzać wieloma zdalnymi instancjami baz danych. Narzędzie znacząco zwiększa produktywność administratorów, np.: poprzez au-tomatyczne wykrywanie powiązań pomiędzy obiektami, przygoto-wanie skryptów dla zadanych zmian. IBM Optim Development Stu-dio 2.2 stanowi środowisko deweloperskie dla programistów SQL i Ja-va. Umożliwia nie tylko tworzenie skryptów SQL/XML i XQuery, ale również tworzenie funkcji zdefiniowanych przez użytkownika oraz procedur SQL PL, PL/SQL i Java, a także ich profilowanie i debugo-wanie. Narzędzie w prostych krokach pozwala przygotować usługi sie-ciowe bez konieczności programowania aplikacji: od stworzenia zapy-tań SQL na testowaniu wdrożonej na serwer aplikacyjny usługi skoń-czywszy. Dodatkowo programiści Javy mogą skorzystać z API IBM pureQuery. Umożliwia ono mapowanie tabel relacyjnych do obiek-tów, z zachowaniem wysokiej wydajności. Dzięki zamienianiu litera-łów znacznikami parametrów, określeniu dozwolonych zapytań, a na-wet konwersji na statyczny kod maksymalnie ograniczone są możli-wości ataków bazodanowych. Na dodatkową optymalizację instrukcji SQL, zapisanych w skryptach bądź zagnieżdżonych w obiektach języ-ka Java, pozwala z kolei IBM Optim queryTuner. Analizując środowi-sko oraz plany poszczególnych zapytań, narzędzie pozwala wychwycić nieoptymalne zapytania i zmodyfikować je do wydajnej postaci. Dzię-ki oparciu produktów rodziny Optim na platformie Eclipse 3.4 moż-

Page 7: Sdj Extra 35 Db2

3D Methods of Display Objects in Flash Player 10

7

liwa jest integracja środowisk dla administratorów baz danych i zespo-łów deweloperskich (wykorzystujących także narzędzia rodziny Ratio-nal). Dodatkowe wsparcie dla pracy grupowej zapewnia również ze-staw wtyczek dla systemów zarządzania wersją.

Niejawna konwersja typówPodobnie do klasycznych języków programowania, DB2 wymusza-ła na programistach używania jawnej konwersji typów w ramach za-pytań SQL. Jednak olbrzymi rozwój języków dynamicznych, takich jak: PHP, Ruby czy Groovy, sprawił, iż programiści zaczęli coraz czę-ściej wykorzystywać słabą konwersję typów w ramach aplikacji. IBM, dostrzegając ten trend, wprowadził w wersji DB2 9.7 niejawną kon-wersję typów. Obejmuje ona swoim zakresem konwersje pomiędzy ty-pami numerycznymi oraz typami czasowymi a łańcuchami znaków. Zmiana typów dotyczy nie tylko operacji przypisania czy porównania wartości, ale obejmuje również większość standardowych funkcji SQL (np. konkatenacje). Co ważne, konwersja odbywa się na typ zmiennej znajdującej się po lewej stronie operatora, dzięki temu programista ma pełną kontrolę nad kodem. Jednocześnie czas przygotowania nowych zapytań ulega skróceniu.

Ewolucja schematów i rewalidacja obiektówCiągły rozwój aplikacji wymusza na projektantach systemów bazodano-wych modyfikacje struktury pojedynczych obiektów bądź całych sche-matów. Zależności pomiędzy obiektami często wymuszały wykona-nie takich operacji w ściśle określony sposób. W ramach DB2 9.7 udo-stępniono mechanizm automatycznej rewalidacji obiektów zależnych. Dzięki temu możliwe jest w jednym kroku usunięcie obiektu i zastą-pienie go nowym. Dzięki instrukcji CREATE OR REPLACE taką czynność można wykonać dla funkcji, procedur, widoków oraz aliasów. Z kolei polecenie ALTER wzbogaciło się o zmianę nazwy i typu (w ramach do-stępnych konwersji) kolumny oraz o możliwość jej usunięcia w trybie online. Sprawia to, że zarówno codzienne prace administratorów, jak i procesy migracji do nowych wersji aplikacji są dużo bardziej wydajne.

DB2 9.7 już certyfikowana dla SAPDB2 przechodzi zestaw rygorystycznych testów na zgodność z syste-mami SAP (ERP, BI) jeszcze przed oficjalną premierą. Zaangażowa-nie inżynierów SAP w proces projektowania i testowania DB2 prak-tycznie eliminuje ryzyko przejścia na nową wersję. Nowe wersje DB2 standardowo dostępne są do wykorzystania z SAP-em w przeciągu 4 do 8 tygodni, co pozwala bardzo szybko skorzystać z nowych tech-nologii. Tak było w przypadku wersji DB2 8.2, 9.1, 9.5. Wersja 9.7 nie jest tutaj wyjątkiem - posiada już certyfikat na zgodność z syste-mami SAP! Najwyższy poziom integracji DB2 i SAP, a także dużo niższe koszty utrzymania czynią DB2 bardzo atrakcyjną nie tylko w przypadku instalacji nowych systemów,ale także dla klientów posia-dających już inne bazy danych. W ciągu ostatniego roku ponad 100 dużych klinetów SAP na świecie zmigrowało swoją bazę do DB2. Proces migracji należy do stosunkowo prostych i jest bardzo dobrze zdefiniowany.

Cennym źródłem informacji w tym zakresie może być bezpłat-na książka DB2 Optimization Techniques for SAP Database Migra-tion And Unicode Conversion, dostępna na stronach http://www.redbo-oks.ibm.com (klucz do wyszukania: SG24 – 7774 – 00).

Rysunek 1. IBM Optim DataBase Administrator 2.2

ARtUR WROńSKIOd piętnastu lat specjalizuje się w rozwiązaniach przetwarzania danych. Aktualnie pracuje jako kierownik zespołu technicznego IBM Information Management Software.Kontakt z autorem: [email protected]

MARCIN MOLAKSpecjalista w zakresie zarządzania danymi w dziale oprogramowania IBM Polska i ambasador programu inicjatywy akademickiej IBM. Odpowiada za wsparcie serwerów danych DB2 i solidDB oraz narzędzi z rodzin Optim i Da-ta Studio. Absolwent Wydziału Fizyki Politechniki Warszawskiej. Prywatnie pasjonat nowych technologii oraz miłośnik fantastyki. Kontakt z autorem: [email protected]

Page 8: Sdj Extra 35 Db2

SDJ Extra 35�

03/2009 (5)

Odpowiednikiem linków bazodano-wych Oracle są w DB2 tzw. pseu-donimy (ang. nicknames) . Aplika-

cja wykorzystująca pseudonimy odwołuje się do zdalnych zasobów w zupełnie przezroczy-sty sposób. Zanim jednak będziemy mogli sięgnąć do bazy Oracle z DB2, musimy odpo-wiednio skonfigurować środowisko.

Pierwszym krokiem konfiguracji jest zain-stalowanie w środowisku DB2 odpowiednich bibliotek dostępu do bazy Oracle. Biblioteki, czyli tzw. opakowania (ang. wrappers), udo-stępniane są w pakiecie hurtowni danych DB2 (produkt InfoSphere Warehouse) bądź w niezależnym produkcie InfoSphere Fede-ration Server. Po zainstalowaniu bibliotek i wgraniu odpowiedniego klucza licencyjne-go powinniśmy uaktywnić obsługę federacji poprzez aktualizację parametru instancji FE-DERATED, tak jak poniżej (zmiana parame-tru wymaga restartu instancji):

db2 update dbm cfg using federated yes

Do zestawienia natywnej metody komuni-kacji z bazą Oracle należy w środowisku in-stancji DB2 dodatkowo zainstalować opro-gramowanie klienta Oracle oraz odpowied-nio skonfigurować uchwyt TNS do bazy da-nych ORACLE. Konfigurację TNS możemy przeprowadzić z użyciem środowiska gra-ficznego ORACLE NetManager (patrz Ry-sunek 1), który dokonuje wpisu do pliku tnsnames.ora znajdującego się przy aplika-cji klienta.

Wpis taki można także dokonać ręcznie. Na Listingu 1 zamieściliśmy przykładową za-wartość pliku tnsnames.ora wygenerowaną narzędziem NetManager z Rysunku 1.

W środowisku DB2 należy także ustawić zmienną ORACLE_HOME, by DB2 było świadome, gdzie zainstalowany jest klient Oracle. Zmienną ORACLE_HOME moż-na także ustawić w pliku {katalog instalacyj-

ny DB2}\cfg\db2dj.ini. Jeżeli na danym kom-puterze zainstalowanych jest kilka aplikacji klienckich Oracle (klient: 10g, 11g, Develo-per), w pliku tym można określić, z którego TNSnames.ora brana będzie nazwa serwisu.

Przykładowy wpis w pliku db2dj.ini:

ORACLE_HOME= C:\oracle\product\10.2.0\

client_1

Mając już skonfigurowanego klienta Oracle, możemy przystąpić do konfiguracji mecha-nizmu federacji.

Integracja DB2 z bazami danych Oracle

In the expanded version of AS3 that ships with Flash CS4, every Display Object is embedded in 3D space, with x, y and z coordinates as well as properties that allow rotation about any of the three coordinate axes and more advanced manipulations.

Listing 1. Przykładowa konfiguracja pliku tnsnames.ora

# tnsnames.ora Network Configuration File:

# C:\oracle\product\10.2.0\client_1\NETWORK\ADMIN\tnsnames.ora

# Generated by Oracle configuration tools.

HW10 =

(DESCRIPTION =

(ADDRESS_LIST =

(ADDRESS = (PROTOCOL = TCP)(HOST = soltek)(PORT = 1521))

)

(CONNECT_DATA =

(SERVICE_NAME = hw)

)

)

Listing 2. Utworzenie przykładowej procedury stowarzyszonej

CREATE PROCEDURE HENRYK.TESTOWA SOURCE SCOTT.TESTOWA

NUMBER OF PARAMETERS 3 FOR SERVER HW10 MODIFIES SQL DATA

NOT DETERMINISTIC EXTERNAL ACTION

Listing 3. Właściwa definicja procedury na serwerze Oracle

CREATE OR REPLACE PROCEDURE TESTOWA

(a1 IN NUMBER, a2 IN VARCHAR2, a3 IN VARCHAR2 DEFAULT 'Lodz') AS

BEGIN

insert into scott.dept values (a1,a2,a3);

exception

when others then rollback;

END;

Page 9: Sdj Extra 35 Db2

3D Methods of Display Objects in Flash Player 10

www.sdjournal.org �

Zdefiniowanie opakowańKonfigurując mechanizm federacji w DB2, musimy określić, z której biblioteki dostę-pu będziemy korzystać – czyli musimy zde-finiować opakowanie. Poniżej przedstawili-śmy instrukcję tworzącą opakowanie o na-zwie NET8, które będzie korzystało z biblio-teki db2net8.dll:

db2 create wrapper net8 library

‘db2net8.dll’;

Biblioteka db2net8.dll korzysta z klienta Oracle. Nazwę NET8 później wykorzysta-my przy definiowaniu parametrów serwera Oracle. Z każdym opakowaniem dostępne są odpowiednie opcje. Ustawienia opakowania dla Oracle zostały przedstawione w Tabeli 1.

Definicja serwera

Po utworzeniu opakowania następnym kro-kiem jest zdefiniowanie serwera, czyli okre-ślenie, z którą bazą danych będziemy się łą-czyć. Serwer definiuje się instrukcją CRE-ATE SERVER, tak jak w przykładzie poniżej:

db2 create server hw type oracle version

‘10g’ wrapper net8 options( add

node ‘HW’)

Najlepiej definicję serwera określić przy wyko-rzystaniu narzędzi graficznych, ponieważ na-rzędzie zaproponuje nam listę dostępnych opcji oraz od razu będziemy mieli możliwość spraw-dzenia połączenia ze zdalną bazą. Przykładowy ekran konfiguracji serwera pokazaliśmy na Ry-sunku 2. Warto kliknąć w przycisk „Właściwo-ści”, by określić dodatkowe opcje definiowanego serwera. W Tabeli 2 pokazaliśmy możliwe do ustawienia zmienne środowiskowe wykorzy-stywane przez definiowany serwer.

Opcje specyficzne dla serwera Oracle przed-stawione zostały w Tabeli 3.

Odwzorowanie użytkownikówW środowisku sfederowanym należy zdefi-niować, w jaki sposób lokalni użytkownicy DB2 będą mapowani na zdalnych użytkow-ników bazy Oracle. Odwzorowanie określa się przy wykorzystaniu instrukcji CREATE USER MAPPING, tak jak poniżej:

CREATE USER MAPPING FOR DB2ADMIN SERVER HW

OPTIONS (ADD REMOTE_AUTHID ‘scott’

, ADD REMOTE_PASSWORD ‘tiger’ ) ;

CREATE USER MAPPING FOR HENRYK SERVER HW

OPTIONS (ADD REMOTE_AUTHID ‘scott’

, ADD REMOTE_PASSWORD ‘tiger’ ) ;

Po pomyślnym wykonaniu powyższych in-strukcji, w momencie połączenia się z serwe-rem Oracle, DB2 będzie automatycznie ma-

Listing 5. ?????????????????

Listing 1. xxxxxxxxxxx

Listing 3. xxxxxxxxxx

Page 10: Sdj Extra 35 Db2

SDJ Extra 3510

03/2009 (5)

Tabela 1. Opcje opakowania dla biblioteki db2net8.dll

Parametr Wart Opis

DB2_FENCED N Określa, czy opakowanie działa w trybie chronionym, czy w trybie zaufanym. Wartością domyślną jest 'N' - opakowanie działa w trybie zaufanym.

DB2_UM_PLUGIN_LANG JAVA Określa język dla wtyczki odwzorowania użytkowników. Poprawne wartości to 'Java' i 'C'. Wartością domyśl-ną jest 'Java'.

DB2_UM_PLUGIN Określa łańcuch, w którym jest rozróżniana wielkość liter, dla nazwy klasy, która odpowiada odwzorowaniu klasy repozytorium przez użytkownika. Na przykład "UżytkownikOdwzorowanieRepozytoriumLDAP".

Tabela 2. Zmienne środowiskowe dla serwera.

Parametr Wartość Opis

ORACLE_BASE Określa katalog główny drzewa katalogów klienta Oracle. Zmienna ta jest opcjonalna. Jeśli zmienna ORACLE_BASE została ustawiona podczas instalowania oprogramowania klienta Oracle, na serwerze sto-warzyszonym należy również ustawić zmienną środowiskową ORACLE_BASE.

ORA_NLS33 Określa lokalizację plików danych NLS dla bazy Oracle wersja �.0, �.1, �.0.1 i �.2. Ta opcja jest opcjonalna.

TNS_ADMIN Określa lokalizację pliku tnsnames.ora, jeśli nie znajduje się on w ścieżce domyślnej. Na platformach Win-dows klient Oracle szuka pliku tnsnames.ora w katalogu \NETWORK\ADMIN. Na platformach UNIX klient szuka pliku tnsnames.ora w katalogu /etc.

ORACLE_HOME C:\oracle\product\10.2.0\client_1

Określa pełną ścieżkę do katalogu, w którym zainstalowane jest oprogramowanie klienta Oracle. Ta opcja jest wymagana.

NLS_LANG Opakowanie używa tej wartości do konwersji strony kodowej Oracle. Opakowanie określa terytorium oraz stronę kodową stowarzyszonej bazy danych. Opakowanie ustawia wartość zmiennej NLS_LANG tak, aby jak najbardziej odpowiadała ona ustawieniom narodowym programu Oracle. Jeśli nie istnieje odpo-wiednie zbliżone ustawienie narodowe, zmiennej NLS_LANG nadawana jest wartość American_Ameri-ca.US7ASCII. Aby uzyskać dostęp do źródła danych, które zawiera dane korzystające z chińskiej strony ko-dowej GB 1�030, stowarzyszona baza danych musi używać strony kodowej UTF-�.

Słownik systemowy dla obiektów sfederowanych baz danychObsługa środowisk stowarzyszonych wymaga od administratorów baz danych nie tylko przygotowywania nowych, ale również wyszukiwa-nia i aktualizacji istniejących obiektów sfederowanych. Funkcje, potrzebne do realizacji powyższych zadań, dostępne są w narzędziu graficz-nych DB2 Control Center w postaci dedykowanych widoków i kreatorów. Pracując jednak w środowisku tekstowym, możemy wykorzystać słow-nik bazodanowy. Schemat SYSCAT udostępnia bowiem widoki na tabele systemowe, zawierające metadane obiektów sfederowanych. Poniżej przedstawiliśmy listę wybranych widoków z krótkim opisem:

• SYSCAT.WRAPPERS – zawiera definicje opakowań;• SYSCAT.WRAPOPTIONS – określa opcje dla każdego z opakowań;• SYSCAT.SERVERS – reprezentuje definicje serwerów (zdalnych źródeł danych);• SYSCAT.SERVEROPTIONS – obejmuje opcje dla każdego z serwerów;• SYSCAT.USEROPTIONS – określa odwzorowania użytkowników;• SYSCAT.NICKNAMES – zawiera definicje pseudonimów;• SYSCAT.FUNCMAPPINGS – reprezentuje mapowanie funkcji;• SYSCAT.FUNCMAPOPTIONS – obejmuje opcje dla zdalnych funkcji;• SYSCAT.TYPEMAPPINGS – określa odwzorowania typów;• SYSCAT.ROUTINESFEDERATED – zawiera definicje stowarzyszonych procedur składowanych;• SYSCAT.COLUMNS – prezentuje definicje kolumn, m.in. dla pseudonimów;• SYSCAT.COLOPTIONS – obejmuje definicje kolumn w zdalnych tabelach.

Celem wyświetlenia informacji (na bazie słownika systemowego) na temat zdefiniowanego w ramach artykułu opakowania posłużyliśmy się za-pytaniem:

SELECT varchar(wrapname,30) as wrapname,

char(wraptype, 1) as wraptype,

varchar(library,20) as library

FROM syscat.wrappers

WRAPNAME WRAPTYPE LIBRARY

-------------------- -------- --------------------

NET8 R libdb2net8.dll

IBM dostarcza również narzędzia db2look, które przygotowuje instrukcje DDL dla obiektów zdefiniowanych w bazie obiektów stowarzyszo-nych. Poniżej przedstawiliśmy komendę tworzącą zapytania w pliku hw.sql dla obiektów sfederowanych w bazie PD:

db2look –d PD –e –fedonly –o hw.sql

Tak przygotowane instrukcje należy jedynie uzupełnić o hasła dla zdalnych użytkowników. Możliwe jest również zawężenie zakresu obiektów sto-warzyszonych do określonego opakowania (przełącznik –wrapper) bądź serwera zdalnego (przełącznik –server).

Page 11: Sdj Extra 35 Db2

3D Methods of Display Objects in Flash Player 10

www.sdjournal.org 11

Tabela 3. Opcje ustawień dla definicji serwera Oracle

Parametr War-tość

Opis

PASSWORD Y Określa, czy do źródła danych mają być wysyłane hasła. 'Y' oznacza, że hasła są zawsze wysyłane do źró-dła danych i poddawane sprawdzaniu poprawności.

DB2_TWO_PHASE_COMMIT Określa, czy na serwerze stowarzyszonym jest włączone dwufazowe zatwierdzanie transakcji z serwe-rem źródła danych.

DB2_MAXIMAL_PUSHDOWN N Określa, czy optymalizator zapytań wybiera plan dostępu przekazujący więcej operacji zapytań do źró-dła danych. Wartością domyślną jest 'N'. Optymalizator zapytań nie przekazuje niżej więcej operacji za-pytań, a zamiast tego wybiera plan o najmniejszym szacowanym koszcie.

PUSHDOWN Y Określa, czy serwer stowarzyszony umożliwia wartościowanie operacji przez źródło danych. Wartością domyślną jest 'Y' (Tak). Wartość 'N' oznacza, że serwer stowarzyszony pobiera kolumny ze zdalnego źró-dła danych i nie zezwala na wartościowanie operacji przez źródło danych.

IUD_APP_SVPT_ENFORCE Y Określa, czy serwer stowarzyszony wymusza korzystanie z punktów zapisu aplikacji. Wartość 'Y' ozna-cza, że serwer stowarzyszony wymusza punkty zapisu i zapobiega wykonywaniu operacji INSERT, UPDA-TE i DELETE na pseudonimach, gdy źródło danych nie obsługuje punktów zapisu aplikacji.

COLLATING_SEQUENCE N Określa, czy źródło danych oraz stowarzyszona baza danych korzystają z tej samej, domyślnej kolejności zestawiania. Aby zagwarantować poprawne wyniki z serwera stowarzyszonego, należy podać wartość 'I', która oznacza, że w źródle danych nie jest rozróżniana wielkość liter.

CPU_RATIO 1.0 Określa, o ile szybciej lub wolniej działa procesor źródła danych w porównaniu z procesorem serwera stowarzyszonego. Dopuszczalne są wartości większe od 0 i mniejsze od 1x10 23. Ustawienia mogą być wyrażane z użyciem dowolnego zapisu stosowanego dla liczb rzeczywistych, na przykład: 123E10, 123 lub 1.21E4.

IO_RATIO 1.0 Określa, o ile szybciej lub wolniej działa procesor źródła danych w porównaniu z procesorem serwera stowarzyszonego. Dopuszczalne są wartości większe od 0 i mniejsze od 1x10 23. Ustawienia mogą być wyrażane z użyciem dowolnego zapisu stosowanego dla liczb rzeczywistych, na przykład: 123E10, 123 lub 1.21E4.

FED_PROXY_USER Określa ID autoryzowanego użytkownika używane do nawiązywania wszystkich wychodzących połą-czeń zaufanych, gdy połączenie przychodzące nie jest zaufane. Użytkownik, którego identyfikator jest określony w tej opcji, musi mieć odwzorowanie użytkownika określające opcje REMOTE_AUTHID i RE-MOTE_PASSWORD.

XA_OPEN_STRING_OPTIONS Określa identyfikator menedżera zasobów rejestru serwera Microsoft SQL Server. Ta opcja jest wymaga-na, gdy opcja DB2_TWO_PHASE COMMIT ma wartość 'Y'.

DB2_UM_PLUGIN_LANG JAVA Określa język dla wtyczki odwzorowania użytkowników. Poprawne wartości to 'Java' i 'C'. Wartością do-myślną jest 'Java'.

DB2_UM_PLUGIN Określa łańcuch, w którym jest rozróżniana wielkość liter, dla nazwy klasy, która odpowiada odwzoro-waniu klasy repozytorium przez użytkownika. Na przykład "UżytkownikOdwzorowanieRepozytoriumL-DAP".

DB2_MAX_ASYNC_REQU-ESTS_PER_QUERY

1 Określa maksymalną liczbę współbieżnych żądań asynchronicznych z zapytania. Poprawne są wartości z zakresu od -1 do 64000. Wartość -1 oznacza, że liczbę żądań definiuje optymalizator stowarzyszonej ba-zy danych. Wartość 0 oznacza, że źródło danych nie może obsługiwać dodatkowych żądań.

NODE HW10 Określa nazwę węzła, na którym znajduje się serwer bazy danych Oracle. Nazwę węzła można uzyskać z pliku tnsnames.ora.

OLD_NAME_GEN N Określa sposób przekształcania nazw kolumn oraz nazw indeksów znajdujących się w źródle danych w pseudonimy nazw kolumn oraz lokalne nazwy indeksów dla stowarzyszonej bazy danych. Wartość 'N' oznacza, że generowane nazwy są bardzo zbliżone do nazw w źródle danych. Wartość 'Y' oznacza, że ge-nerowane nazwy są takie same jak nazwy tworzone w wersji � i wcześniejszych. A zatem nazwy mogą nie być zbliżone do nazw źródła danych. Wartością domyślną jest 'N'.

COMM_RATE 2 Określa szybkość transmisji, w megabajtach na sekundę, między serwerem stowarzyszonym a serwerem źródła danych. Dopuszczalne są wartości większe od 0 i mniejsze od 21474�364�. Wartość musi być wy-rażona jako liczba całkowita, na przykład 12.

FOLD_PW Określa wielkość liter hasła wysyłanego do źródła danych. Jeśli nie została określona żadna wartość, ser-wer stowarzyszony wysyła hasło z użyciem wielkich liter, a następnie, jeśli takie hasło nie zostanie zaak-ceptowane, serwer wysyła je z użyciem małych liter.

FOLD_ID Określa wielkość liter identyfikatora użytkownika wysyłanego do źródła danych. Jeśli nie została okre-ślona żadna wartość, serwer stowarzyszony wysyła identyfikator użytkownika z użyciem wielkich liter, a następnie, jeśli taki identyfikator nie zostanie zaakceptowany, serwer wysyła identyfikator użytkownika z użyciem małych liter.

VARCHAR_NO_TRAILING_BLANKS

N Opcja ta ma zastosowanie dla źródeł danych zawierających dane znakowe o zróżnicowanej długości bez uzupełniania spacjami na końcu. Opcję tę należy ustawić w przypadku, gdy ma mieć ona zastosowanie do wszystkich kolumn typu VARCHAR i VARCHAR2 w obiektach źródeł danych dostępnych ze wskazane-go serwera.

Page 12: Sdj Extra 35 Db2

SDJ Extra 3512

03/2009 (5)

pować lokalnego użytkownika db2admin na zdalnego użytkownika scott, oraz użytkow-nika henryk także na użytkownika scott. Dla zdalnego użytkownika należy także podać ha-sło, które będzie przechowane w lokalnej bazie

DB2 (istnieje także możliwość przechowania hasła w wybranym, centralnym systemie uwie-rzytelniania). Na Rysunku 3 przedstawiliśmy widok graficznego narzędzia wykorzystywa-nego do definiowania odwzorowań użytkow-

ników. Dostępne opcje dla odwzorowań użyt-kowników przedstawiliśmy w Tabeli 4.

Definicja pseudonimówOkreślenie definicji pseudonimów dla do-wolnych tabel ze zdalnej bazy danych realizu-je się instrukcją CREATE NICKNAME . Po-niżej przedstawiamy przykładowe instrukcje definicji pseudonimów dla tabel ze zdalnego schematu SCOTT:

CREATE NICKNAME HENRYK.DEPT1 FOR

HW.SCOTT.DEPT;

CREATE NICKNAME HENRYK.EMP1 FOR

HW.SCOTT.EMP;

CREATE NICKNAME HENRYK.BONUS1 FOR

HW.SCOTT.BONUS;

CREATE NICKNAME HENRYK.SALGRADE1 FOR

HW.SCOTT.SALGRADE;

Do pseudonimów można odwoływać się do-kładnie w taki sam sposób jak do lokalnych obiektów. Wykonanie lokalnego zapytania na tabeli HENRYK.DEPT1 spowoduje automa-tyczne podłączenie się do zdalnej bazy HW i wykonanie zapytania na tabeli SCOTT.DEPT, wykorzystując użytkownika scott i jego hasło wcześniej zapisane w lokalnej bazie.

Stowarzyszone procedury składowaneUdostępnienie zdalnej procedury składowanej w lokalnym środowisku realizuje się instruk-cją CREATE PROCEDURE z odpowiednimi parametrami wskazującymi na zdalny serwer Oracle. Na Listingu 2 przedstawiliśmy przy-kładową instrukcję definicji procedury stowa-rzyszonej HENRYK.TESTOWA, będącej od-wołaniem do zdalnej procedury TESTOWA na serwerze Oracle (Listing 3).Uruchamianie zdalnej procedury w lokal-nym środowisku DB2 wygląda dokładnie tak samo jak uruchamianie innych lokalnych pro-cedur:

call testowa(12,’aaa’,’bbb’);

Definiowanie zdalnych tabelMechanizmy federacji DB2 umożliwiają tak-że tworzenie tabel i ograniczeń integralnościo-wych na zdalnym serwerze. Na Rysunkach 4 oraz 5 przedstawiliśmy ekrany narzędzia gra-ficznego DB2 Control Center (widok: Obiek-ty stowarzyszonej bazy danych, Tabele Zdalne), które wykorzystaliśmy do utworzenia zdalnej tabeli. Na Listingu 4 przedstawiliśmy właściwą instrukcję SQL tworzenia tabeli, która została wygenerowana przez to narzędzie. Warto zwró-cić uwagę, że parametry lokalizacji danej tabeli, takie jak nazwa serwera czy schemat w zdalnej bazie, podaje się w sekcji OPTIONS. Na Listin-gu 5 pokazaliśmy, że ograniczenia itegralnościo-we na zdalnym obiekcie tworzy się w podobny sposób jak dla lokalnych tabel.

Listing 4. xxxxxxxxxxx

Figure 3. A Rotating Cube

Figure 4. The Cube Sides

Page 13: Sdj Extra 35 Db2

3D Methods of Display Objects in Flash Player 10

www.sdjournal.org 13

Przykłady rozproszonych operacjiTak jak wspomnieliśmy, dostęp do zdalnych tabel odbywa się poprzez lokalnie zdefiniowa-ne pseudonimy. Dla deweloperów aplikacji nie ma różnicy, czy odwołują się do lokalnej tabe-li, czy do pseudonimu, który w rzeczywistości jest tabelą zarządzaną przez zewnętrzną bazę danych. Poniżej przedstawiliśmy zapytanie, które realizuje złączenie dwóch tabel: HEN-RYK.EMP1 oraz HENRYK. DOSTAWCY.

SELECT ENAME, SAL, DNAME

FROM HENRYK.EMP1 E

INNER JOIN HENRYK.DOSTAWCY D

ON E.DEPTNO=D.DEPTNO ORDER BY 2 DESC;

Analizując treść zapytania, trudno jest po-wiedzieć, czy mamy do czynienia z lokal-nymi, czy zdalnymi tabelami. Dopiero po sprawdzeniu planu wykonania zapytania (patrz Rysunek 6) można dojść do wniosku, że tabela EMP1 jest zdalnym obiektem, po-nieważ w planie zapytania pojawia się blok SHIP, który oznacza, że ta część przetwa-rzania jest przekazana na serwer zewnętrz-

ny. Pobierając szczegóły danej tabeli, dostęp-ne w planie wykonania zapytania (np. klika-jąc na element HENRYK.EMP1 z Rysunku 6), będziemy mogli sprawdzić fizyczne po-łożenie tabeli.

W ramach sfederowanej bazy danych mo-żemy nie tylko wykonywać zapytania roz-proszone, ale także realizować rozproszone transakcje. Poniżej przedstawiliśmy przykład transakcji, która wstawia rekord do lokalnej tabeli DEPT oraz zdalnej DEPT1:

update command options using c off;

insert into dept1

values (100,’dept1’,’W-wa’);

insert into dept

values (101,’dept2’,’W-wa’);

commit;

Pierwsze polecenie wyłącza opcję auto-matycznego zatwierdzania, więc obie in-strukcje INSERT będą wykonane jako jed-na transakcja (instrukcje należy przekopio-wać do pliku, a następnie uruchomić db2 –tvf nazwa_pliku). W tym miejscu musimy wspomnieć, że podczas definiowania zdal-

nego serwera mamy możliwość określenia, czy dla danego źródła obsługiwane będzie dwufazowe zatwierdzanie (parametr DB2_TWO_PHASE_COMMIT z Tabeli 3). Do-myślnie włączone jest tylko jednofazowe za-twierdzanie, co oznacza, że w danej transak-cji można modyfikować tylko obiekty z jed-nego serwera (zdalnego albo lokalnego). By przedstawiony powyżej kod zakończył się z sukcesem, należy wcześniej włączyć dwufa-zowe zatwierdzanie.

PosumowanieW artykule przedstawiliśmy podstawowe kroki, jakie należy podjąć, by zintegrować ba-zę DB2 z bazą Oracle. Mechanizm federacji DB2 pozwala wirtualnie włączyć do lokalnej bazy danych obiekty pochodzące od zdalnych baz DB2, Oracle czy baz innych producen-tów. Wykorzystanie mechanizmów federacji DB2 może znacznie przyspieszyć tworzenie aplikacji, ponieważ ukrywa przez programi-stami różnice interfejsów i dodatkowo opty-malizuje rozproszone operacje odczytu i zapi-su. Mechanizmy federacji DB2 wykorzystuje się bardzo często w hurtowniach danych do szybkiego prototypowania transformacji da-nych. Mechanizm ten jest używany także do realizacji rozproszonej hurtowni danych, w której część danych celowo pozostaje w syste-mach źródłowych, przy zachowaniu dostępu do tych danych.

Tabela 4. Opcje ustawień odwzorowania użytkownika

Parametr Wartość Opis

USE_TRUSTED_CONTEXT N Określa, czy odwzorowanie użytkownika jest zaufane. Wartość 'Y' oznacza, że odwzorowa-nie użytkownika jest zaufane i może być wykorzystane zarówno dla zaufanych, jak i niezau-fanych połączeń wychodzących. Domyślnie odwzorowania użytkownika nie są zaufane i mogą być wykorzystywane tylko dla niezaufanych połączeń wychodzących.

REMOTE_PASSWORD ******** Określa hasło dla opcji REMOTE_AUTHID. Poprawnym ustawieniem jest dowolny łańcuch o długości 32 znaków lub mniejszej. Jeśli opcja ta nie jest określona, do źródła danych nie zo-stanie przesłane żadne hasło.

FED_PROXY_USER Określa ID autoryzowanego użytkownika używane do nawiązywania wszystkich wychodzą-cych połączeń zaufanych, gdy połączenie przychodzące nie jest zaufane. Użytkownik, któ-rego identyfikator jest określony w tej opcji, musi mieć odwzorowanie użytkownika określa-jące opcje REMOTE_AUTHID i REMOTE_PASSWORD. Jeśli zostanie określona opcja odwzo-rowania użytkownika FED_PROXY_USER, należy określić również opcję.

REMOTE_AUTHID scott Określa identyfikator użytkownika zdalnego do odwzorowania na identyfikator użytkow-nika lokalnego. Poprawnym ustawieniem jest dowolny łańcuch o długości 255 znaków lub mniejszej. Jeśli ta opcja nie jest określona, zostanie użyty zdalny identyfikator łączący się ze stowarzyszoną bazą danych.

Paweł DrZyMałaHenryk weLFLePaweł Drzymała i Henryk Welfle są pracownika-mi Politechniki Łódzkiej. Od 12 lat zajmują się sys-temami relacyjnych baz danych oraz integracją środowisk heterogenicznych baz danych. Ich za-interesowania obejmują również tworzenie za-awansowanych aplikacji klient-serwer z dostę-pem do wydajnych źródeł bazodanowych.Kontakt z autorami: [email protected]; [email protected];

Listing 4. Przykład instrukcji utworzenia zdalnej tabeli

CREATE TABLE HENRYK.DEPT_REMOTE (

DEPTNO BIGINT NOT NULL ,

DEPTNAME VARCHAR (20) NOT NULL,

LOC VARCHAR (18) WITH DEFAULT 'Lodz')

OPTIONS (

REMOTE_SERVER 'HW10', REMOTE_SCHEMA 'SCOTT',

REMOTE_TABNAME 'DEPARTMENT1');

COMMENT ON TABLE HENRYK.DEPT_REMOTE IS 'tabela na Oraclu';

Listing 5. Przykład instrukcji tworzącej klucz podstawowy na zdalnej tabeli

ALTER TABLE HENRYK.DEPT_REMOTE

ADD CONSTRAINT PK_DEPT_REMOTE PRIMARY KEY ( DEPTNO) ;

Page 14: Sdj Extra 35 Db2

14

Tworzenie aplikacji

SDJ Extra 35

A rtykuł ten wprowadzi Cię w zagad-nienia związane z tworzeniem pro-cedur składowanych w języku SQL

PL na serwerze DB2. Śledząc zamieszczo-ne w artykule przykłady, poznasz podstawo-we zasady tworzenia procedur, dowiesz się, jak definiować parametry i zmienne oraz jak używać instrukcji blokowych i sterują-cych. Ponadto dowiesz się, jak stosować kur-sory oraz zapoznasz się z obsługą błędów i dy-namicznym SQL-em. Zamieszczone w arty-kule przykłady są przygotowane dla serwe-ra DB2 w wersji 9.5 oraz 9.7. Przed przystą-pieniem do dalszych ćwiczeń zainstaluj na swoim komputerze serwer DB2, najlepiej w polskiej wersji językowej. Kody źródłowe do wszystkich przykładów znajdziesz na dołą-czonej płytce.

NarzędziaDo tworzenia, uruchamiania i testowa-nia przykładowych procedur potrzebny Ci będzie edytor tekstowy, procesor pole-ceń SQL i testowa baza danych. Do edycji procedur możesz użyć swojego ulubionego edytora tekstowego, natomiast do urucha-miania procedur składowanych proponu-ję wykorzystać procesor poleceń CLP (ang. Command Line Processor), który jest dostęp-ny po zainstalowaniu serwera DB2. Proce-sor poleceń pod systemem Windows uru-chomisz, wydając w oknie komend polece-nie db2cmd. Pod systemem Linux procesor poleceń działa bezpośrednio w oknie ter-minala. Do testowania procedur będzie Ci również potrzebna baza danych. Skrypt do utworzenia testowej bazy danych zamieści-łem na Listingu 1. Zapisz treść tego skryptu w pliku baza.sql, a następnie wywołaj na-stępujące polecenia w oknie procesora po-leceń CLP:

db2 create database stock

db2 connect to stock

db2 –tf baza.sql

Po utworzeniu testowa baza danych bę-dzie zawierała jedną tabelę, która symu-luje mini magazyn produktów (w naszym

przypadku będą to głównie owoce). Każ-dy produkt ma swój identyfikator, nazwę, stan magazynowy, cenę i kod promocyj-ny. Przy tej okazji chciałbym zwrócić Two-ją uwagę na typ pola price. Typ DECFLOAT, dostępny w DB2 od wersji 9.5, jest specjal-nie zaprojektowany do obliczeń waluto-wych, nie powoduje błędów zaokrągleń i automatycznie rozszerza swój zakres. Uży-łem tego typu, ponieważ w kilku przypad-kach podczas testowania procedur otrzy-mywałem błędy przekroczenia zakresu i byłem zmuszony rozszerzać zakres dla ty-pu DECIMAL.

Pierwsza proceduraZacznijmy zatem od utworzenia pierwszej procedury składowanej. Na Listingu 2 przed-stawiłem najprostszą procedurę o nazwie simple. Zapisz treść tej procedury w pliku p01.sql i skompiluj, wydając polecenie:

db2 -td@ -vf p01.sql

W efekcie wykonania tego polecenia proce-sor poleceń db2 wczyta plik p01.sql (opcja -f), traktując znak @ (opcja -td@) jako zakoń-czenie treści procedury, a następnie wyświe-tli tekst tej procedury (opcja -v) oraz komu-nikat informujący o rezultacie kompilacji. Jeśli kompilacja się powiodła, to możesz uru-chomić tę procedurę poleceniem:

db2 call simple()

Jedynym efektem jej działania jest komu-nikat:

Status powrotu= 0

Przyjrzyjmy się treści tej procedury. De-klarację procedury składowanej rozpo-czyna instrukcja CREATE PROCEDURE, po której następuje jej nazwa i lista parame-trów ujęta w nawiasy okrągłe. Dalej znaj-dują się opcjonalne klauzule SPECIFIC oraz LANGUAGE, a po nich w instrukcji bloko-wej BEGIN/END następuje ciało procedu-ry. Deklarację procedury kończy znak ter-

minatora @. Nazwa procedury może mieć co najwyżej 128 znaków, a lista parame-trów procedury może być pusta, wtedy nawiasy okrągłe można pominąć. Klauzu-la SPECIFIC definiuje dodatkową, unikal-ną nazwę procedury, którą można wyko-rzystać do rozróżniania procedur, które mają taką samą nazwę, ale różnią się licz-bą parametrów. Nazwę specyficzną mo-żesz wykorzystać do usunięcia procedu-ry składowanej poleceniem DROP SPECIFIC PROCEDURE, na przykład:

db2 drop specific procedure p01

co z pewnością przyda Ci się podczas eks-perymentowania z kolejnymi przykłada-mi. Nazwa specyficzna pozwala także jed-noznacznie wskazać procedurę, która ma być zaktualizowana przy wykorzystaniu in-strukcji CREATE OR REPLACE PROCEDURE (DB2 9.7). Klauzula LANGUAGE wskazuje, że pro-cedura jest napisana w języku SQL (w DB2 procedury składowane mogą być tworzone również w innych językach, takich jak C czy Java oraz po włączeniu trybu kompatybilno-ści Oracle w PL/SQL).

SQL w procedurze składowanejNa Listingu 3 przedstawiłem kod, który po-kazuje, jak w procedurze składowanej moż-na stosować instrukcje SQL-owe. W wier-szu {1} za pomocą instrukcji DELETE usuwa-ne są wszystkie rekordy z tabeli stock, a w wierszu {2} instrukcją INSERT są dodawa-ne nowe rekordy. Zwróć uwagę na instruk-cję INSERT, która w jednym wywołaniu do-daje sześć rekordów produktów do naszego magazynu. Przy tej okazji chciałbym rów-nież zwrócić Twoją uwagę na sposób użycia komentarzy. W SQL PL masz do dyspozycji dwa rodzaje komentarzy: jednowierszowe i wielowierszowe. Komentarze jednowierszo-we zaczynają się od podwójnego myślnika i mogą wystąpić w dowolnym miejscu proce-dury. Komentarze wielowierszowe zaczyna-ją się od znaków /* i kończą znakami */ tak jak w języku C, ale mogą wystąpić tylko w ciele procedury.

Procedury składowane w DB2 9Dariusz Depta

Page 15: Sdj Extra 35 Db2

Procedury składowane w DB2 9

15www.sdjournal.org

Jak już się pewnie domyślasz, procedu-ra clean będzie nam często służyła do przy-gotowywania początkowych danych do eks-perymentów z procedurami składowanymi. Zapisz treść procedury z Listingu 3 do pliku p02.sql i skompiluj, wywołując:

db2 -td@ -vf p02.sql

Po uruchomieniu tej procedury poleceniem:

db2 call clean()

w tabeli stock powinno pojawić się sześć różnych rodzajów owoców, co łatwo spraw-dzisz, wywołując polecenie:

db2 select * from stock

Parametry procedurDo procedury składowanej możesz przeka-zać listę parametrów. Parametry mogą być wejściowe IN, wejściowo-wyjściowe INOUT oraz wyjściowe OUT. Typy przekazywanych parametrów muszą być zgodne z typami da-nych języka SQL, a poszczególne parametry muszą być od siebie oddzielone przecinka-mi. Zwyczajowo nazwy parametrów poprze-dzamy prefiksem p_, tak aby nie myliły się z nazwami zmiennych i nazwami kolumn. Na Listingu 4 przedstawiłem procedurę, która przyjmuje cztery parametry: dwa wejścio-we w wierszach {1} i {2}, jeden wyjściowy w wierszu {3} i jeden wejściowo-wyjściowy w wierszu {4}. Procedura calc_value oblicza wartość produktu zwracaną w parametrze wyjściowym p_value na podstawie podanej na wejściu ilości p_amount i ceny jednostko-wej p_price. Dodatkowo wartość produktu jest dodawana do sumy częściowej p_total, która jest aktualizowana przy każdym wy-wołaniu procedury. Obliczenia są wykony-wane w wierszach {5} i {6}, a na koniec w wierszu {7}, za pomocą instrukcji RETURN zwracany jest status wykonania procedury (tzw. status powrotu). Przyjmuje się, że war-tość zero oznacza poprawne wykonanie pro-cedury, wartości ujemne oznaczają błąd in-strukcji SQL, natomiast wartości dodatnie oznaczają umowny błąd wynikający z logiki przetwarzania. W instrukcji RETURN pro-cedura może zwracać tylko liczby całkowite. Zapisz treść tej procedury w pliku p03.sql i skompiluj poleceniem:

db2 -td@ -vf p03.sql

Sprawdź jej działanie, podając następujące parametry w wywołaniu:

db2 call calc_value(100,1.2,?,200.0)

Powinieneś uzyskać rezultat podobny do te-go poniżej:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_VALUE

Wartość parametru: 120,00

Nazwa parametru: P_TOTAL

Wartość parametru: 320,00

Status powrotu= 0

Zgodnie z oczekiwaniami, wartość towaru wynosi 120, a suma częściowa 320. Zwróć uwagę, że w wywołaniu procedury para-metr wyjściowy został zastąpiony znakiem zapytania. Procesor CLP zinterpretował i wyświetlił wartości wszystkich parametrów wyjściowych i wejściowo-wyjściowych prze-kazanych w wywołaniu procedury. Zauważ, że procedura calc _ value do obliczeń wy-korzystuje wyłącznie przekazane parame-try. Bardziej skomplikowane obliczenia mo-gą jednak wymagać użycia zmiennych, więc przyjrzyjmy się, w jaki sposób możesz je sto-sować.

ZmienneW języku SQL PL każda zmienna przed użyciem musi być zadeklarowana instrukcją DECLARE. Deklaracje muszą wystąpić na po-czątku instrukcji blokowej po słowie BEGIN, a typy zmiennych powinny odpowiadać ty-pom bazy danych DB2. Domyślnie zmien-ne są inicjalizowane wartością nieokreślo-ną NULL, chyba że w deklaracji występuje klauzula DEFAULT, która ustawia inną war-tość początkową zmiennej. Zwyczajowo na-zwy zmiennych poprzedza się przedrost-kiem v_ dla łatwiejszego odróżnienia ich od nazw parametrów i nazw kolumn. Do ustawiania wartości zmiennych możesz wy-korzystać szereg instrukcji, takich jak SET, VALUES, SELECT lub GET DIAGNOSTICS. Przy-kłady użycia trzech pierwszych instrukcji do ustawiania zmiennych przedstawiłem w procedurze z Listingu 5. Instrukcję GET DIAGNOSTICS opisałem nieco później, przy okazji omawiania obsługi błędów. Zapisz

Listing 1. Testowa baza danych

-- baza.sql

CREATE TABLE stock

(

id BIGINT NOT NULL,

name VARCHAR(12) NOT NULL,

amount INTEGER NOT NULL DEFAULT 0,

price DECFLOAT DEFAULT 0.0,

promo SMALLINT DEFAULT 0

);

ALTER TABLE stock ADD CONSTRAINT stock_name_pk

PRIMARY KEY (name);

ALTER TABLE stock ALTER COLUMN id

SET GENERATED BY DEFAULT AS IDENTITY (START WITH 7);

ALTER TABLE stock ADD CONSTRAINT stock_amount_chk

CHECK (amount >= 0);

ALTER TABLE stock ADD CONSTRAINT stock_price_chk

CHECK (price >= 0.0);

ALTER TABLE stock ADD CONSTRAINT stock_promo_chk

CHECK (promo in (0,1,2,3));

CREATE TYPE NAME_ARRAY AS VARCHAR(30) array[100];

CREATE TYPE AMOUNT_ARRAY AS INTEGER array[100];

CREATE TYPE PRICE_ARRAY AS INTEGER array[100];

Listing 2. Najprostsza procedura

-- p01.sql

CREATE PROCEDURE simple()

SPECIFIC p01

LANGUAGE SQL

BEGIN

END

@

Page 16: Sdj Extra 35 Db2

16

Tworzenie aplikacji

SDJ Extra 35

treść tej procedury w pliku p04.sql i skom-piluj poleceniem:

db2 -td@ -vf p04.sql

Procedura get _ info pobiera informacje o produkcie i zwraca je w parametrach wyj-ściowych. Uruchom tę procedurę, wywołu-jąc na przykład:

db2 call get_info('orange',?,?,?,?,?)

W efekcie jej działania powinieneś otrzymać wynik podobny do poniższego:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_AMOUNT

Wartość parametru: 10

Nazwa parametru: P_PRICE

Wartość parametru: 1.05

Nazwa parametru: P_VALUE

Wartość parametru: 10.50

Nazwa parametru: P_DATE

Wartość parametru: 06/21/2009

Nazwa parametru: P_TIME

Wartość parametru: 00:23:32

Status powrotu= 0

Przyjrzyjmy się treści tej procedury. Zmienne zadeklarowane są na początku instrukcji blokowej, zaraz po słowie BEGIN. W wierszu {1} zadeklarowana jest zmienna typu INTEGER z wartością ustawianą do-myślnie na NULL. Zmienna w wierszu {2} jest zainicjowana wartością 0,0 w klau-zuli DEFAULT. W wierszu {3} do pobrania ilości i ceny produktu wykorzystałem in-

strukcję SELECT, w której w klauzuli INTO podaje się odpowiednie zmienne, do któ-rych będą zapisane wartości kolumn ze znalezionego rekordu. Instrukcja SELECT w takim przypadku musi zwracać tylko je-den rekord. Zauważ, że wartości mogą być w ten sam sposób zapisywane do zmien-nych lokalnych, jak i do parametrów wyj-ściowych. Do przypisywania wartości do zmiennych możesz wykorzystać instruk-cję SET, tak jak w wierszu {4}. Instrukcja VALUES w wierszu {5} posłużyła mi do po-brania daty z predefiniowanego rejestru CURRENT DATE, a w wierszach {6} i {7} do wyliczenia określonej wartości i przypi-sania do zmiennej, na przykład CURRENT TIME – 1 HOUR. Polecenia VALUES oraz SET mogą również być wykorzystane do jedno-czesnego przypisania wartości do więcej niż jednej zmiennej, np.:

SET (p_amount, v_value) = (10, 12)

Jak widzisz, DB2 udostępnia szeroki wa-chlarz możliwości wykorzystania zmien-nych – z większością z nich spotkasz się jesz-cze w kolejnych przykładach procedur.

Złożony SQL – instrukcja blokowaInstrukcja blokowa grupuje pojedyncze in-strukcje języka SQL PL, które umieszcza się pomiędzy słowami kluczowymi BEGIN i END. Jeśli przypomnisz sobie pierwszą procedu-rę z Listingu 2, to przekonasz się, że ciało tej procedury to po prostu pusta instrukcja blo-kowa. Instrukcje blokowe możesz zagnież-dżać – przykład takiej procedury przedsta-wiłem na Listingu 6. W wierszu {1} od sło-wa BEGIN rozpoczyna się deklaracja ciała procedury, która kończy się słowem END w wierszu {12}. Wewnątrz procedury znajdu-je się zagnieżdżona instrukcja blokowa, któ-ra rozpoczyna się w wierszu {4}, a kończy w wierszu {11}. Zwróciłeś już pewnie uwa-gę na to, że w obu instrukcjach blokowych, na ich początku są zadeklarowane zmienne. Warto pamiętać, że wszystkie zasady doty-czące deklarowania zmiennych, kursorów i innych obiektów wewnątrz instrukcji blo-kowych obowiązują również w blokach za-gnieżdżonych.

Złożony SQL – etykietyInstrukcja blokowa i dowolne wyrażenie SQL-owe mogą być opatrzone etykietą, co również pokazałem na Listingu 6. Etykie-ty poprawiają czytelność kodu, ale przede wszystkim pozwalają przemieścić się w dowolne miejsce wewnątrz procedury. Po-nadto etykiety są wykorzystywane w pę-tli LOOP, którą się zajmiemy nieco później. Instrukcje blokowe w wierszach {1} i {4} na Listingu 6 zostały opatrzone etykieta-

Listing 3. SQL w procedurze składowanej

-- p02.sql

CREATE PROCEDURE clean()

SPECIFIC p02

LANGUAGE SQL

BEGIN

-– Delete all existing products.

DELETE FROM stock; --{1}

/*

Insert initial number of products to stock table.

All products will be added at once, using single

INSERT statement.

*/

INSERT INTO stock VALUES --{2}

(1, 'orange', 10, 1.05, 0),

(2, 'lemon', 30, 2.10, 1),

(3, 'lime', 60, 3.15, 1),

(4, 'grapes', 90, 4.20, 2),

(5, 'apple', 120, 5.25, 2),

(6, 'banana', 150, 6.30, 3);

END

@

Listing 4. Parametry procedur

-- p03.sql

CREATE PROCEDURE calc_value(

IN p_amount INTEGER, --{1}

IN p_price DECFLOAT, --{2}

OUT p_value DECFLOAT, --{3}

INOUT p_total DECFLOAT) --{4}

LANGUAGE SQL

SPECIFIC p03

BEGIN

SET p_value = p_amount * p_price; --{5}

SET p_total = p_total + p_value; --{6}

RETURN 0; --{7}

END

@

Page 17: Sdj Extra 35 Db2

Procedury składowane w DB2 9

17www.sdjournal.org

mi o nazwach odpowiednio l_1 i l_2. Za-uważ, że etykiety umieściłem przed sło-wem BEGIN, a dla przejrzystości kodu te sa-me etykiety podałem po słowie END dla każ-dego bloku. W wierszach {9} i {10} umie-ściłem etykiety o nazwach l_3 i l_4 przed instrukcjami SQL-owymi, dzięki czemu mógłbym przeskoczyć do nich z innego miejsca procedury za pomocą instrukcji GOTO. Zauważ, że na początku każdego blo-ku zadeklarowałem dwie zmienne o takich samych nazwach. Zmienne zadeklarowane w zagnieżdżonym bloku w wierszach {5} i {6} przesłoniły zmienne zadeklarowane w bloku nadrzędnym w wierszach {2} i {3}. W takich przypadkach pomocne są właśnie etykiety umieszczone przy instrukcjach blokowych. Do przesłoniętej zmiennej mo-żesz odwołać się, używając etykiety z nad-rzędnej instrukcji blokowej jako prefiksu, tak jak pokazałem to w wierszach {7} i {8}.

Złożony SQL – ATOMICInstrukcja blokowa może być opatrzona klauzulą ATOMIC lub NOT ATOMIC. Użycie klauzuli ATOMIC powoduje, że wszystkie polecenia SQL-owe wewnątrz bloku będą traktowane jako jedna całość, co oznacza, że jeśli gdzieś wewnątrz bloku wystąpi nie-obsłużony błąd, to wszystkie instrukcje w tym bloku zostaną wycofane, nawet te, któ-re wykonały się poprawnie. W procedurze na Listingu 6 instrukcja blokowa rozpoczy-nająca się w wierszu {4} ma podaną klauzu-lę ATOMIC, natomiast blok w wierszu {1} jej nie ma, co jest równoznaczne z podaniem klauzuli NOT ATOMIC. Jak się pewnie domy-ślasz, klauzula NOT ATOMIC powoduje, że in-strukcje wewnątrz bloku nie są traktowa-ne jako jedna całość. Jeśli wewnątrz takie-go bloku wystąpi nieobsłużony błąd, to in-strukcje, które do miejsca wystąpienia te-go błędu wykonały się poprawnie, nie zo-staną automatycznie wycofane. W takim przypadku musisz ręcznie anulować je in-strukcją ROLLBACK czy też ROLLBACK TO

SAVEPOINT. Jeśli ma to sens, możesz oczy-wiście zatwierdzić dotychczasowe zmia-ny instrukcją COMMIT. Sprawdźmy działa-nie klauzuli ATOMIC w praktyce. W tym ce-lu zapisz procedurę z Listingu 6 w pliku p05.sql i skompiluj poleceniem:

db2 -td@ -vf p05.sql

Procedura ta służy do sprzedaży produk-tów parami, ale jeśli oba produkty nie występują w żądanych ilościach na maga-zynie, to nie możemy sprzedać żadnego z nich. Aby uzyskać taki efekt, instruk-cje SQL-owe w wierszach {9} i {10}, któ-re aktualizują ilość produktów, są obję-te instrukcją blokową z klauzulą ATOMIC. Dodam tylko, że próba sprzedaży więk-

szej ilości produktu niż jest dostępna na magazynie spowoduje wygenerowanie błędu, ponieważ ilość produktu nie mo-że być mniejsza od zera (zobacz warunek

stock _ amount _ chk na Listingu 1). Przed uruchomieniem procedury sell przygotuj dane początkowe, wywołując znaną już z Listingu 3 procedurę clean:

Listing 5. Zmienne w procedurze składowanej

-- p04.sql

CREATE PROCEDURE get_info(

IN p_name VARCHAR(12),

OUT p_amount INTEGER,

OUT p_price DECFLOAT,

OUT p_value DECFLOAT,

OUT p_date DATE,

OUT p_time TIME

)

LANGUAGE SQL

SPECIFIC p04

BEGIN

DECLARE v_amount INTEGER; --{1}

DECLARE v_value DECFLOAT DEFAULT 0.0; --{2}

SELECT amount, price --{3}

INTO v_amount, p_price

FROM stock

WHERE name = p_name;

SET p_amount = v_amount; --{4}

VALUES CURRENT DATE INTO p_date; --{5}

VALUES (CURRENT TIME – 1 HOUR) INTO p_time; --{6}

VALUES (v_amount * p_price) INTO p_value; --{7}

END

@

Listing 6. Złożony SQL

-- p05.sql

CREATE PROCEDURE sell(

IN p_name1 VARCHAR(12),

IN p_amount1 INTEGER,

IN p_name2 VARCHAR(12),

IN p_amount2 INTEGER

)

LANGUAGE SQL

SPECIFIC p05

l_1: BEGIN --{1}

DECLARE v_amount1 INTEGER; --{2}

DECLARE v_amount2 INTEGER; --{3}

SELECT amount INTO v_amount1

FROM stock WHERE name = p_name1;

SELECT amount INTO v_amount2

FROM stock WHERE name = p_name2;

l_2: BEGIN ATOMIC --{4}

DECLARE v_amount1 INTEGER; --{5}

DECLARE v_amount2 INTEGER; --{6}

SET v_amount1 = l_1.v_amount1 – p_amount1; --{7}

SET v_amount2 = l_1.v_amount2 – p_amount2; --{8}

l_3: UPDATE stock SET amount = v_amount1 --{9}

WHERE name = p_name1;

l_4: UPDATE stock SET amount = v_amount2 --{10}

WHERE name = p_name2;

END l_2; --{11}

END l_1 --{12}

@

Page 18: Sdj Extra 35 Db2

18

Tworzenie aplikacji

SDJ Extra 35

db2 call clean()

i sprawdź stan magazynowy, wywołując:

db2 select name, amount from stock

Ilości dostępnych produktów powinny być takie jak pokazane poniżej:

NAME AMOUNT

------------ -----------

orange 10

lemon 30

lime 60

grapes 90

apple 120

banana 150

Teraz spróbujmy sprzedać wszystkie poma-rańcze i cytryny:

db2 call sell('orange',10,'lemon',30)

Ponownie sprawdź stan magazynowy, wy-wołując:

db2 select name, amount from stock

Powinieneś uzyskać rezultat podobny do po-kazanego poniżej:

NAME AMOUNT

------------ -----------

orange 0

lemon 0

lime 60

grapes 90

apple 120

banana 150

Pomarańcze i cytryny zostały w całości sprzedane, ich ilości na magazynie są zero-we. Teraz spróbujmy sprzedać wszystkie li-monki i więcej winogron niż jest dostępnych na magazynie, wywołując:

db2 call sell('lime',30,'grapes',91)

SQL0545N Żądana operacja nie jest

dopuszczalna, ponieważ wiersz nie

spełnia ograniczenia sprawdzenia

"STOCK.STOCK_AMOUNT_CHK".

SQLSTATE=23513

Tak jak się spodziewaliśmy, próba sprzeda-ży winogron zakończyła się wygenerowa-

niem błędu. Sprawdź zatem, jak wygląda obecnie stan magazynowy, ponownie wy-wołując:

db2 select name, amount from stock

Wynik powinien wyglądać tak jak poniżej:

NAME AMOUNT

------------ -----------

orange 0

lemon 0

lime 60

grapes 90

apple 120

banana 150

Zgodnie z naszymi oczekiwaniami zarów-no stan ilościowy limonek, jak i winogron nie uległ zmianie, ponieważ błąd wygenero-wany w wierszu {10} spowodował wycofa-nie zmian dokonanych w wierszu {9}, a sta-ło się tak dlatego, że obie instrukcje UPDATE objęte są blokiem z klauzulą ATOMIC. War-to pamiętać, że instrukcje blokowe z klau-zulą ATOMIC nie mogą być zagnieżdżane, a ponadto wewnątrz takiego bloku nie mo-

Listing 7. Instrukcja warunkowa IF

-- p06.sql

CREATE PROCEDURE get_prices(

IN p_name VARCHAR(12),

OUT p_price DECFLOAT,

OUT p_promo1 DECFLOAT,

OUT p_promo2 DECFLOAT,

OUT p_promo3 DECFLOAT

)

LANGUAGE SQL

SPECIFIC p06

BEGIN

DECLARE v_amount INTEGER;

DECLARE v_promo SMALLINT;

SELECT amount, price, promo

INTO v_amount, p_price, v_promo

FROM stock WHERE name = p_name;

IF p_price > 5.0 THEN --{1}

SET p_promo1 = p_price * 0.95;

END IF;

IF v_amount > 50 THEN --{2}

SET p_promo2 = p_price * 0.85;

ELSE

SET p_promo2 = NULL;

END IF;

IF v_promo = 1 THEN --{3}

SET p_promo3 = p_price * 0.9;

ELSEIF v_promo = 2 THEN

SET p_promo3 = p_price * 0.8;

ELSEIF v_promo = 3 THEN

SET p_promo3 = p_price * 0.7;

ELSE

SET p_promo3 = p_price;

END IF;

END

@

Listing 8. Instrukcja warunkowa CASE – wariant 1

-- p07.sql

CREATE PROCEDURE get_discount_price(

IN p_name VARCHAR(12),

OUT p_price DECFLOAT,

OUT p_promo3 DECFLOAT

)

LANGUAGE SQL

SPECIFIC p07

BEGIN

DECLARE v_promo SMALLINT;

SELECT promo, price INTO v_promo, p_price

FROM stock WHERE name = p_name;

CASE v_promo --{1}

WHEN 1 THEN --{2}

SET p_promo3 = p_price * 0.9;

WHEN 2 THEN --{3}

SET p_promo3 = p_price * 0.8;

WHEN 3 THEN --{4}

SET p_promo3 = p_price * 0.7;

ELSE --{5}

SET p_promo3 = p_price * 1.0;

END CASE;

END

@

Page 19: Sdj Extra 35 Db2

Procedury składowane w DB2 9

19www.sdjournal.org

żesz użyć instrukcji COMMIT, ROLLBACK i SAVEPOINTS. Blok ATOMIC można traktować jak pojedynczą, nierozerwalną instrukcję SQL, która zbudowana jest z wielu elemen-tów. Na koniec nadmienię, że niezależnie od rodzaju wykorzystanego bloku ATOMIC bądź NOT ATOMIC do programisty należy de-cyzja o finalnym zatwierdzeniu bądź wy-cofaniu wykonanych operacji (np. COMMIT bądź ROLLBACK w programie po wykona-niu procedury). W analizowanym przykła-dzie wywołanie procedury inicjowane by-ło z tekstowego interpretera, który w do-myślnej konfiguracji automatycznie dodaje COMMIT po każdej interpretowanej instruk-cji (tu: po wywołaniu procedury instruk-cją CALL).

Instrukcje warunkoweInstrukcje warunkowe umożliwiają zmia-nę działania procedury w zależności od tego, czy jest spełniony określony waru-nek. W SQL PL dostępne są dwie instruk-cje warunkowe: IF oraz CASE. W instruk-cji IF jako warunek musisz podać wyraże-nie logiczne (wartość prawda lub fałsz), natomiast w instrukcji CASE jako waru-nek możesz wykorzystać wartość pojedyn-czej zmiennej (na przykład liczbę całko-witą). Przykład wykorzystania instrukcji IF zamieściłem na Listingu 7. W przykła-dzie tym obliczane są trzy ceny promocyj-ne: pierwsza cena p_promo1 zależy od ce-ny jednostkowej, druga p_promo2 od ilości towaru na magazynie, a trzecia p_promo3 od kodu promocyjnego. W najprostszym przypadku, tak jak w wierszu {1}, instruk-cja IF wykonuje określony ciąg czynności, jeśli podany warunek logiczny jest spełnio-ny. W wierszu {1}, jeśli cena jednostko-wa przekracza 5.00 PLN, to pierwsza ce-na promocyjna zostanie obniżona o 5%, w przeciwnym razie cena nie zostanie zmie-niona i nadal będzie miała wartość NULL. Zwróć uwagę, że warunek został umiesz-czony pomiędzy słowami IF oraz THEN, a ciąg instrukcji do wykonania umieszczo-ny został pomiędzy słowami THEN oraz END IF. Z kolei jeśli chcesz, aby pewne instruk-cje zostały wykonane, kiedy warunek jest spełniony, a inne, gdy warunek nie jest spełniony, to możesz wykorzystać słowo kluczowe ELSE. Instrukcje przed słowem ELSE będą wykonane, jeśli warunek jest spełniony, a po słowie ELSE, gdy warunek nie jest spełniony. W przykładzie pokaza-nym w wierszu {2} na Listingu 7 cena pro-duktu zostanie obniżona o 15%, jeśli jego ilość na magazynie przekracza 50, w prze-ciwnym razie cena promocyjna p_promo2 będzie nieokreślona (wartość NULL). W ostatnim przypadku instrukcję IF mo-żesz wykorzystać do wykonania wielu wy-stępujących po sobie porównań z użyciem

słowa kluczowego ELSEIF. Przykład uży-cia ELSEIF pokazałem w wierszu {3} na Listingu 7, gdzie kilkakrotnie sprawdzam kod promocyjny (dla wartości 1, 2 i 3), i na jego podstawie odpowiednio zmniej-szam cenę o 10, 20 i 30 procent. Jeśli kod promocyjny ma wartość 0, to na końcu tej instrukcji IF w klauzuli ELSE ustawiam ce-nę promocyjną na początkową cenę pro-duktu. Sprawdźmy w takim razie działa-nie instrukcji IF w praktyce. Zapisz proce-durę z Listingu 7 do pliku p06.sql i skom-piluj, wywołując:

db2 -td@ -vf p06.sql

Przygotuj dane początkowe, uruchamiając procedurę clean:

db2 call clean()

i sprawdź ceny promocyjne na przykład dla bananów, wywołując:

db2 call get_prices('banana',?,?,?,?)

Powinieneś otrzymać następujący wynik:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_PRICE

Listing 9. Instrukcja warunkowa CASE – wariant 2

-- p08.sql

CREATE PROCEDURE can_delete_product(

IN p_name VARCHAR(12)

)

SPECIFIC p08

LANGUAGE SQL

BEGIN

DECLARE v_amount INTEGER DEFAULT -1;

SELECT amount INTO v_amount

FROM stock WHERE name = p_name;

CASE --{1}

WHEN v_amount = 0 THEN RETURN 0; --{2}

WHEN v_amount > 0 THEN RETURN -1; --{3}

ELSE RETURN -2; --{4}

END CASE;

END

@

Listing 10. Pętla WHILE

-- p09.sql

CREATE PROCEDURE avg_while(

IN p_start INTEGER,

IN p_end INTEGER,

OUT p_avg DECFLOAT

)

LANGUAGE SQL

SPECIFIC p09

BEGIN

DECLARE v_sum DECFLOAT DEFAULT 0.0;

DECLARE v_counter INTEGER DEFAULT 0;

DECLARE v_current INTEGER;

SET v_current = p_start;

WHILE (v_current <= p_end) DO --{1}

SET v_sum = v_sum + v_current;

SET v_current = v_current + 1;

SET v_counter = v_counter + 1;

END WHILE; --{2}

SET p_avg = v_sum / v_counter; --{3}

END

@

Page 20: Sdj Extra 35 Db2

20

Tworzenie aplikacji

SDJ Extra 35

Wartość parametru: 6.30

Nazwa parametru: P_PROMO1

Wartość parametru: 5.9850

Nazwa parametru: P_PROMO2

Wartość parametru: 5.3550

Nazwa parametru: P_PROMO3

Wartość parametru: 4.410

Wszystkie ceny promocyjne zostały wyli-czone, ponieważ dla przypadku z wiersza {1} cena jest większa od 5.0, dla przypad-ku {2} ilość przekracza 50, a w przypadku {3} kod promocyjny jest równy 3. Możesz poeksperymentować z pobieraniem cen promocyjnych dla innych produktów, aby

sprawdzić sytuacje, kiedy poszczególne in-strukcje warunkowe się nie wykonają.

Jak pewnie zauważyłeś, w ostatnim przy-kładzie w wierszu {3} cena zależy bezpo-średnio od kodu promocyjnego będącego liczbą całkowitą o wartościach od 0, 1, 2 i 3. W takim przypadku do obliczenia ce-ny promocyjnej możesz użyć instrukcji wa-runkowej CASE, co pokazałem na Listin-gu 8. Zauważ, że kod promocyjny v_promo występuje tylko jeden raz po słowie CASE w wierszu {1}, natomiast porównywane war-tości są podawane po słowie WHEN w ko-lejnych wierszach {2}, {3} i {4}. Po słowie THEN w każdym warunku następują in-strukcje do wykonania w przypadku gdy kod promocyjny ma określoną wartość. In-strukcja CASE może mieć podaną klauzu-lę ELSE, tak jak w wierszu {5}, która zosta-nie wykonana, jeśli nie został spełniony ża-den wcześniejszy warunek. Sprawdźmy za-tem ponownie cenę promocyjną bananów, ale z wykorzystaniem procedury z Listin-gu 8. Zapisz treść tej procedury do pliku p07.sql i skompiluj:

db2 -td@ -vf p07.sql

a następnie wywołaj:

db2 call get_discount_price('banana',?,?)

Jak można było się spodziewać, wynik bę-dzie taki sam jak poprzednio dla trzeciej ce-ny promocyjnej:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_PRICE

Wartość parametru: 6.30

Nazwa parametru: P_PROMO3

Wartość parametru: 4.410

W procedurze na Listingu 9 pokazałem jeszcze jeden wariant instrukcji CASE, w którym po słowie CASE w wierszu {1} nie występuje liczba całkowita, natomiast po każdym słowie WHEN w wierszach {2} i {3} może być użyty dowolny warunek logicz-ny. Dla formalności dodam, że procedura can _ delete _ product sprawdza ilość pro-duktu na magazynie i jeśli jego ilość wynosi zero, to zwraca 0, jeśli ilość jest większa od zera, to zwraca -1, natomiast jeśli produk-tu w ogóle nie ma na magazynie, to zwra-ca -2. Zwróć uwagę, że jeśli produkt nie wy-stępuje na magazynie, to wartość zmiennej v _ amount nie zostanie określona przez in-strukcję SELECT INTO (zapytanie nie zwró-ci żadnego rekordu) i pozostanie taka sa-ma jak po jej zadeklarowaniu, czyli -1. W przykładzie z Listingu 9 zademonstrowa-łem możliwość wykorzystania instrukcji RETURN do przekazania stanu magazyno-

Listing 11. Pętla REPEAT

-- p10.sql

CREATE PROCEDURE avg_repeat(

IN p_start INTEGER,

IN p_end INTEGER,

OUT p_avg DECFLOAT

)

LANGUAGE SQL

SPECIFIC p10

BEGIN

DECLARE v_sum DECFLOAT DEFAULT 0.0;

DECLARE v_counter INTEGER DEFAULT 0;

DECLARE v_current INTEGER;

SET v_current = p_start;

REPEAT --{1}

SET v_sum = v_sum + v_current;

SET v_current = v_current + 1;

SET v_counter = v_counter + 1;

UNTIL (v_current > p_end) --{2}

END REPEAT; --{3}

SET p_avg = v_sum / v_counter;

END

@

Listing 12. Pętla LOOP

-- p11.sql

CREATE PROCEDURE avg_loop(

IN p_start INTEGER,

IN p_end INTEGER,

OUT p_avg DECFLOAT

)

LANGUAGE SQL

SPECIFIC p11

BEGIN

DECLARE v_sum DECFLOAT DEFAULT 0.0;

DECLARE v_counter INTEGER DEFAULT 0;

DECLARE v_current INTEGER;

SET v_current = p_start;

L1: LOOP --{1}

SET v_sum = v_sum + v_current;

SET v_counter = v_counter + 1;

IF (v_current >= p_end) THEN

LEAVE L1; --{2}

ELSE

SET v_current = v_current + 1;

ITERATE L1; --{3}

END IF;

END LOOP; --{4}

SET p_avg = v_sum / v_counter;

END

@

Page 21: Sdj Extra 35 Db2

Procedury składowane w DB2 9

21www.sdjournal.org

wego. Dobrą praktyką jest jednak wykorzy-stanie tej instrukcji wyłącznie do celów dia-gnostycznych, a nie aplikacyjnych. Skompi-luj tę procedurę po zapisaniu jej do pliku o nazwie p08.sql:

db2 -td@ -vf p08.sql

i uruchom dla nieistniejącego produktu, na przykład dla arbuzów:

db2 call can_delete_product('watermelon')

Wynik będzie oczywiście następujący:

Status powrotu= -2

PętleW języku SQL PL dostępne są pętle WHILE, REPEAT, LOOP oraz FOR. Pętle WHILE i REPEAT wykorzystujemy w przypadkach, kiedy nie wiemy, ile razy będziemy iterować zanim nie wejdziemy do pętli, natomiast pętli FOR możesz użyć do iterowania po zbiorze wy-nikowym opartym na instrukcji SELECT. Z kolei instrukcja LOOP może Ci posłużyć do implementacji pętli ogólnego przezna-czenia. Działanie pętli WHILE, REPEAT oraz LOOP prześledzimy na przykładzie oblicza-nia średniej arytmetycznej. Jako ciekawost-kę pokażę Ci również, jak zrealizować pętlę LOOP za pomocą instrukcji GOTO. Na Listin-gu 10 zamieściłem procedurę wykorzystują-cą pętlę WHILE. Zapisz treść tej procedury w pliku p09.sql i skompiluj poleceniem:

db2 -td@ -vf p09.sql

Sprawdź działanie tej procedury, wydając polecenie:

db2 call avg_while(1,10,?)

Na konsoli pojawi się wynik przedstawio-ny poniżej:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_AVG

Wartość parametru: 5,50

Status powrotu= 0

Wyliczona średnia to 5,50. Przyjrzyjmy się treści procedury z Listingu 10. Składnia pętli jest następująca: w wierszu {1} roz-poczyna się słowem kluczowym WHILE, po którym występuje warunek pętli oraz sło-wo kluczowe DO, a po nim umieszcza się ciało pętli zakończone słowami END WHILE – wiersz {2}. Warunek pętli WHILE jest sprawdzany przed wykonaniem jakiejkol-wiek instrukcji umieszczonej w ciele pętli. W naszym przypadku ma to pewne konse-kwencje: jeśli liczba początkowa p _ start

jest większa od liczby końcowej p _ end, to niestety w wierszu {3} wystąpi błąd dzie-lenia przez zero, co łatwo sprawdzić, wy-wołując:

db2 call avg_while(10,1,?)

SQL0801N Próbowano wykonać dzielenie

przez zero. SQLSTATE=22012

Dzieje się tak dlatego, że licznik v _ counter ma wartość domyślną 0 i nie jest nigdy zwiększany, gdyż warunek wejścia do pętli nie jest w tym przypadku spełniony. Na Li-stingu 11 przedstawiłem ten sam algorytm

zaimplementowany z użyciem pętli REPEAT. Zapisz procedurę z Listingu 11 do pliku p10.sql i skompiluj:

db2 -td@ -vf p10.sql

Po uruchomieniu:

db2 call avg_repeat(1,10,?)

otrzymasz ten sam wynik, czyli 5,50. Sprawdź, co się stanie, jeśli liczba początko-wa będzie większa od liczby końcowej, wy-wołując:

Listing 13. Instrukcja GOTO

-- p12.sql

CREATE PROCEDURE avg_goto(

IN p_start INTEGER,

IN p_end INTEGER,

OUT p_avg DECFLOAT

)

LANGUAGE SQL

SPECIFIC p12

BEGIN

DECLARE v_sum DECFLOAT DEFAULT 0.0;

DECLARE v_counter INTEGER DEFAULT 0;

DECLARE v_current INTEGER;

SET v_current = p_start;

L1: BEGIN

SET v_sum = v_sum + v_current;

SET v_counter = v_counter + 1;

IF (v_current >= p_end) THEN

GOTO L2;

ELSE

SET v_current = v_current + 1;

GOTO L1;

END IF;

END;

L2: SET p_avg = v_sum / v_counter;

END

@

Listing 14. Pętla FOR

-- p13.sql

CREATE PROCEDURE get_total(

OUT p_total DECFLOAT

)

SPECIFIC p13

LANGUAGE SQL

BEGIN

SET p_total = 0.0;

FOR v_row AS SELECT amount, price FROM stock --{1}

DO

SET p_total = p_total + v_row.amount * v_row.price; --{2}

END FOR;

END

@

Page 22: Sdj Extra 35 Db2

22

Tworzenie aplikacji

SDJ Extra 35

db2 call avg_repeat(10,1,?)

Obliczona średnia wynosi 10,0 i nie po-jawia się błąd dzielenia przez zero. Dzie-je się tak dlatego, że warunek pętli jest sprawdzany na jej końcu, po wykonaniu wszystkich instrukcji pomiędzy wiersza-

mi {1} i {2}, więc licznik v _ counter jest zwiększany co najmniej raz. Dla formalno-ści dodam, że deklaracja pętli rozpoczyna się słowem REPEAT {1}, po którym następu-ją instrukcje będące ciałem pętli, następ-nie po słowie UNTIL umieszczamy waru-nek pętli {2}, a całą deklarację kończymy

słowami END REPEAT {3}. Spójrz teraz na Li-sting 12, na którym przedstawione jest to samo rozwiązanie z wykorzystaniem pętli LOOP. Różni się ono od dwóch poprzednich tym, że nie posiada klauzuli deklarującej warunek kończący pętlę. Z założenia LOOP {1} jest nieskończoną pętlą, a do jej stero-wania służą instrukcje ITERATE {3}, LEAVE {2} oraz GOTO. Instrukcja ITERATE rozpo-czyna kolejną iterację od początku pę-tli, LEAVE opuszcza daną pętlę, natomiast GOTO pozwala przemieścić się w dowolny punkt procedury. Pętla musi być opatrzo-na etykietą, jak w wierszu {1}. Ciało pętli kończą słowa END LOOP w wierszu {4}. Pętla LOOP może okazać się pomocna w sytuacji, gdy musisz zaprogramować kilka różnych metod jej opuszczenia. Trzeba jednak pa-miętać, aby stosować tę pętlę z rozwagą i nie dopuścić do sytuacji, w której pętla nigdy się nie zakończy. Zapisz procedurę z Listingu 12 do pliku p11.sql i skompi-luj poleceniem:

db2 -td@ -vf p11.sql

Policz ponownie średnią liczb od 1 do 10:

db2 call avg_loop(1,10,?)

Otrzymany wynik to oczywiście 5,50. Dla porównania, na Listingu 13 przedstawiłem tę samą pętlę zrealizowaną za pomocą in-strukcji GOTO. Wywołanie polecenia GOTO po-woduje przeskok programu do miejsca ozna-czonego podaną etykietą. Z racji tego, że używanie instrukcji GOTO nie należy do do-brych praktyk programistycznych, przykład ten potraktuj jako ciekawostkę.

Do iterowania po zbiorze wynikowym opartym na instrukcji SELECT służy pętla FOR, której przykład zastosowania przedstawiłem na Listingu 14. Procedura get_total sumu-je całkowitą wartość produktów na podsta-wie ich ilości i ceny jednostkowej. Zapisz treść procedury z Listingu 14 do pliku p13.sql i skompiluj poleceniem:

db2 -td@ -vf p13.sql

Wywołanie procedury:

db2 call get_total(?)

wyświetli całkowitą wartość towarów, na przykład:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_TOTAL

Wartość parametru: 2215,50

Wróćmy do Listingu 14. Deklaracja pętli rozpoczyna się słowem FOR, po którym na-

Listing 15. Odczyt danych z użyciem kursora

-- p14.sql

CREATE PROCEDURE get_total_value(

OUT p_value DECFLOAT

)

SPECIFIC p14

LANGUAGE SQL

BEGIN

DECLARE v_amount INTEGER DEFAULT 0;

DECLARE v_price DECFLOAT DEFAULT 0.0;

DECLARE SQLSTATE CHAR(5) DEFAULT '00000'; --{1}

DECLARE c_stock CURSOR FOR --{2}

SELECT amount, price FROM stock;

SET p_value = 0.0;

OPEN c_stock; --{3}

FETCH FROM c_stock INTO v_amount, v_price; --{4}

WHILE (SQLSTATE = '00000') DO --{5}

SET p_value = p_value + v_amount * v_price; --{6}

FETCH FROM c_stock INTO v_amount, v_price; --{7}

END WHILE;

CLOSE c_stock; --{8}

END

@

Listing 16. Modyfikacja danych za pomocą kursora

-- p15.sql

CREATE PROCEDURE sell_bundle(

IN p_amount INTEGER

)

SPECIFIC p15

LANGUAGE SQL

BEGIN

DECLARE v_amount INTEGER DEFAULT 0;

DECLARE SQLSTATE CHAR(5) DEFAULT '00000';

DECLARE c_stock CURSOR FOR

SELECT amount FROM stock

FOR UPDATE; --{1}

OPEN c_stock;

FETCH FROM c_stock INTO v_amount;

WHILE (SQLSTATE = '00000') DO

IF (p_amount >= v_amount) THEN --{2}

DELETE FROM stock

WHERE CURRENT OF c_stock; --{3}

ELSE

UPDATE stock SET amount = v_amount – p_amount

WHERE CURRENT OF c_stock; --{4}

END IF;

FETCH FROM c_stock INTO v_amount;

END WHILE;

CLOSE c_stock;

END

@

Page 23: Sdj Extra 35 Db2

Procedury składowane w DB2 9

23www.sdjournal.org

stępuje nazwa pętli, a po niej definiujemy zapytanie SQL-owe zwracające zbiór wy-nikowy, po którym będziemy iterować. Po-między słowami DO i END FOR umieszcza-my ciało pętli. Pętla będzie się wykony-wała tak długo, dopóki nie zostaną odczy-tane wszystkie rekordy zbioru wynikowe-go. Zwróć uwagę na to, że dostęp do ko-lumn bieżącego rekordu w zbiorze wyniko-wym jest możliwy przez prefiksowanie na-zwy kolumny nazwą pętli, na przykład v _

row.amount.

KursoryW praktyce często spotykamy się z sytuacją, kiedy na określonym zbiorze rekordów mu-simy wykonać dość złożoną logikę bizneso-wą, analizując kolejno rekord po rekordzie. W takich przypadkach z pomocą przycho-dzą nam kursory. W tym podrozdziale po-każę, jak można wykorzystać kursor do po-bierania danych do obliczeń (w trybie tyl-ko do odczytu), a w kolejnych wyjaśnię, jak stosować kursory do modyfikacji danych i przekazywania danych do procedur wywo-łujących. Kursor możemy sobie wyobrazić jako wskaźnik, który przesuwa się kolejno po każdym rekordzie, udostępniając warto-ści jego kolumn. Przed użyciem musimy za-deklarować kursor instrukcją DECLARE, a na-stępnie otworzyć poleceniem OPEN. Otwar-cie kursora oznacza przygotowanie zestawu rekordów (wykonanie instrukcji SELECT) i ustawienie kursora przed pierwszym re-kordem. Do przesuwania się po kolejnych rekordach służy polecenie FETCH, które naj-częściej wywołuje się w pętli aż do przejścia przez wszystkie rekordy. W warunku pętli najczęściej stosujemy zmienną SQLSTATE do sprawdzenia, czy został już osiągnięty ostat-ni rekord. Na koniec, kiedy kursor nie jest już potrzebny, zamykamy go poleceniem CLOSE. Na Listingu 15 przedstawiłem proce-durę get_total_value, która wykorzystuje kursor do obliczenia sumarycznej wartości wszystkich produktów w magazynie (tak, dokładnie to samo, co wcześniej robiliśmy za pomocą pętli FOR). Skompiluj tę procedu-rę poleceniem:

db2 -td@ -vf p14.sql

a następnie uruchom, wywołując:

db2 call get_total_value(?)

W wyniku działania procedura zwróci w parametrze p _ value sumaryczną wartość wszystkich produktów:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_VALUE

Wartość parametru: 2215.50

Przyjrzyjmy się bliżej treści tej procedury. W wierszu {2} zadeklarowałem kursor o nazwie c _ stock, który będzie odczytywał wszyst-kie rekordy zwrócone przez instrukcję:

SELECT amount, price FROM stock;

Zwróć uwagę, że deklarację kursora umie-ściłem po deklaracjach zmiennych – takie jest wymaganie języka SQL PL. Wykona-nie instrukcji SELECT i przygotowanie ze-stawu rekordów dla kursora odbywa się w momencie, kiedy w wierszu {3} otwie-ram kursor instrukcją OPEN. Po otwarciu

kursor jest ustawiony przed pierwszym rekordem, dlatego w wierszu {4} wywo-łuję instrukcję FETCH, która ustawia kur-sor na pierwszym rekordzie i jednocześnie przepisuje do zmiennych v _ amount i v _

price wartości z kolumn amount i price. Do odczytywania pozostałych rekordów wykorzystałem w wierszu {5} pętlę WHILE, w której w wierszu {7} kolejny raz wywo-łuję polecenie FETCH. W warunku pętli, do sprawdzenia, czy kursor osiągnął już ostat-ni rekord, użyłem zadeklarowanej w wier-szu {1} zmiennej SQLSTATE. Po każdym po-prawnym wykonaniu instrukcji FETCH (w

Listing 17. Zachowanie kursora z COMMIT i ROLLBACK

-- p16.sql

CREATE PROCEDURE hold_test()

SPECIFIC p16

LANGUAGE SQL

BEGIN

DECLARE v_amount INTEGER;

DECLARE c_stock CURSOR WITH HOLD FOR --{1}

SELECT amount FROM stock

ORDER BY id

FOR UPDATE OF amount; --{2}

OPEN c_stock;

FETCH FROM c_stock INTO v_amount;

UPDATE stock SET amount = v_amount + 1000

WHERE CURRENT OF c_stock;

COMMIT; --{3}

FETCH FROM c_stock INTO v_amount;

UPDATE stock SET amount = v_amount + 2000

WHERE CURRENT OF c_stock;

COMMIT; --{4}

FETCH FROM c_stock INTO v_amount;

UPDATE stock SET amount = v_amount + 3000

WHERE CURRENT OF c_stock;

ROLLBACK; --{5}

FETCH FROM c_stock INTO v_amount; --{6}

END

@

Listing 18. Zwracanie zbioru wynikowego za pomocą kursora

-- p17.sql

CREATE PROCEDURE double_amounts()

SPECIFIC p17

LANGUAGE SQL

DYNAMIC RESULT SETS 1 --{1}

BEGIN

UPDATE stock SET amount = amount * 2; --{2}

BEGIN

DECLARE c_stock CURSOR WITH RETURN FOR --{3}

SELECT name, amount FROM stock;

OPEN c_stock; --{4}

END;

END

@

Page 24: Sdj Extra 35 Db2

24

Tworzenie aplikacji

SDJ Extra 35

wierszach {4} i {7}) zmienna SQLSTATE jest ustawiana przez DB2 na wartość ‘00000'. Jeśli po otwarciu kursora okazałoby się, że nie ma żadnych rekordów w zestawie bądź wystąpi błąd dostępu do danych, to po wywołaniu instrukcji FETCH w wierszu {4} zmienna SQLSTATE miałaby wartość róż-ną od ‘00000' i pętla nie wykonałaby się ani razu. Z kolei jeśli kursor jest ustawio-

ny na ostatnim rekordzie, to wywołanie FETCH w wierszu {7} spowoduje ustawienie SQLSTATE na wartość różną od ‘00000' i na-stąpi opuszczenie pętli. Wartość zmiennej SQLSTATE tak naprawdę oznacza stan po-prawności wykonania dowolnej instrukcji SQL, gdzie ciąg znaków o wartości ‘00000' oznacza poprawne wykonanie się danej in-strukcji. Wartość tej zmiennej powinna

być sprawdzana zaraz po wykonaniu da-nej instrukcji, dlatego też pomiędzy in-strukcją FETCH a sprawdzeniem zmiennej SQLSTATE nie można umieszczać żadnych innych instrukcji, ponieważ wpłyną one na wartość SQLSTATE. Dla formalności do-dam, że wewnątrz pętli, w wierszu {6}, ob-liczam wartość każdego produktu i dodaję do parametru p _ value, który zwraca su-maryczną wartość wszystkich produktów. Po wyjściu z pętli w wierszu {8} zamykam kursor poleceniem CLOSE. Dobrą praktyką jest zamykanie kursorów, gdy nie są już po-trzebne, ale nawet jeśli tego nie zrobisz, to DB2 zamknie za Ciebie wszystkie otwarte wewnątrz procedury kursory tuż przed jej zakończeniem. W powyższym przykładzie pokazałem, jak wykorzystać kursor do od-czytu danych po kolei ze wszystkich rekor-dów i jak je wykorzystać do dalszych obli-czeń. W następnym przykładzie przedsta-wię, jak za pomocą kursora możesz mody-fikować i usuwać poszczególne rekordy.

Kursory – FOR UPDATEW procedurach składowanych dane najczę-ściej są modyfikowane za pomocą instruk-cji UPDATE lub DELETE. W tych przypadkach operacje są wykonywane jednocześnie na wszystkich rekordach spełniających waru-nek podany w klauzuli WHERE. Stosując kur-sory, możesz selektywnie wykonywać ope-racje modyfikacji lub usuwania danych w zależności od wyniku bardziej złożonych obliczeń, niż te, które są dostępne w klau-zuli WHERE. Na Listingu 16 przedstawiłem procedurę, która przy użyciu kursora usu-wa rekord lub go modyfikuje w zależności od tego, czy spełniony jest określony waru-nek. Aby modyfikacja danych za pomocą kursora była możliwa, w wierszu {1} zade-klarowałem kursor z klauzulą FOR UPDATE. W wierszu {2} sprawdzam warunek, czy ilość towaru podana w parametrze p_

amount przekracza ilość towaru z bieżące-go rekordu zapisaną w zmiennej v_amount. Jeśli tak, to w wierszu {3} usuwam cały re-kord. W przeciwnym przypadku w wierszu {4} zmniejszam ilość dostępnego towaru, modyfikując bieżący rekord. Operacje usu-wania i modyfikacji są zawsze wykonywane na rekordzie, na którym jest aktualnie usta-wiony kursor. Zwróć uwagę, że w klauzuli WHERE w wierszach {3} i {4} występuje klau-zula CURRENT OF z nazwą kursora, które-go dotyczy operacja. Operację modyfikacji wykonuję w pętli dla wszystkich rekordów, co możemy zinterpretować jako sprzedanie wszystkich towarów w ilości mniejszej lub równej tej, którą podajemy w parametrze wejściowym procedury. Wypróbujmy za-tem tę procedurę w praktyce. Zapisz treść procedury z Listingu 16 do pliku p015.sql i skompiluj poleceniem:

Listing 19. Otrzymywanie zbioru wynikowego z kursora

-- p18.sql

CREATE PROCEDURE quad_amounts()

SPECIFIC p18

LANGUAGE SQL

DYNAMIC RESULT SETS 1

BEGIN

DECLARE v_name CHAR(12);

DECLARE v_amount INTEGER;

DECLARE SQLSTATE CHAR(5) DEFAULT '00000';

DECLARE rs_amounts RESULT_SET_LOCATOR VARYING; --{1}

CALL double_amounts(); --{2}

ASSOCIATE RESULT SET LOCATOR (rs_amounts) --{3}

WITH PROCEDURE double_amounts;

ALLOCATE c_amounts CURSOR FOR --{4}

RESULT SET rs_amounts;

FETCH FROM c_amounts INTO v_name, v_amount;

WHILE (SQLSTATE = '00000') DO

UPDATE stock SET amount = v_amount * 2 --{5}

WHERE name = v_name;

FETCH FROM c_amounts INTO v_name, v_amount;

END WHILE;

CLOSE c_amounts; --{6}

BEGIN --{7}

DECLARE c_amounts CURSOR WITH RETURN FOR

SELECT name, amount FROM stock;

OPEN c_amounts;

END;

END

@

Listing 20. Sprawdzanie kodu błędu za pomocą zmiennych SQLSTATE I SQLCODE

-- p19.sql

CREATE PROCEDURE update_price(

IN p_name VARCHAR(12),

IN p_price DECFLOAT,

OUT p_state CHAR(5),

OUT p_code INTEGER

)

SPECIFIC p19

LANGUAGE SQL

BEGIN

DECLARE SQLSTATE CHAR(5) DEFAULT '99999'; --{1}

DECLARE SQLCODE INTEGER DEFAULT 99999; --{2}

UPDATE stock SET price = p_price --{3}

WHERE name = p_name;

VALUES (SQLSTATE,SQLCODE) INTO p_state, p_code; --{4}

END

@

Page 25: Sdj Extra 35 Db2

Procedury składowane w DB2 9

25www.sdjournal.org

db2 -td@ -vf p15.sql

Przygotuj dane początkowe, wywołując pro-cedurę clean:

db2 call clean()

Po jej wykonaniu na magazynie powinno być sześć produktów, co sprawdzisz zapytaniem:

db2 select name,amount from stock

NAME AMOUNT

------------ -----------

orange 10

lemon 30

lime 60

grapes 90

apple 120

banana 150

Teraz wywołaj procedurę sell _ bundle z wartością 70:

db2 call sell_bundle(70)

i sprawdź stan magazynowy:

db2 select name,amount from stock

NAME AMOUNT

------------ -----------

grapes 20

apple 50

banana 80

Procedura zadziałała zgodnie z oczekiwania-mi. Produkty, których ilość była mniejsza, niż 70, zostały usunięte, natomiast w pozo-stałych przypadkach ilość produktu została pomniejszona o 70.

Kursory – WITH HOLDWewnątrz procedury składowanej mo-żesz używać poleceń COMMIT/ROLLBACK do „ręcznego” sterowania transakcjami. Jeśli wraz z tymi poleceniami używasz kurso-rów, to musisz zwrócić uwagę na to, że za-chowanie kursora będzie różne w zależno-ści od tego, czy kursor został zadeklarowa-ny z klauzulą WITH HOLD, czy też nie. Kur-sor zadeklarowany bez klauzuli WITH HOLD zostanie zamknięty po każdym wywołaniu COMMIT lub ROLLBACK, a wszystkie związa-ne z nim zasoby zostaną zwolnione. Mo-że to być trochę uciążliwe, ponieważ po każdym poleceniu COMMIT musiałbyś po-nownie otwierać kursor i przesuwać się do określonego rekordu, by wykonać na nim kolejne operacje. W tej sytuacji pomocna jest klauzula WITH HOLD. Kursor zadekla-rowany z klauzulą WITH HOLD po wywoła-niu COMMIT pozostanie otwarty i nadal bę-dzie wskazywał na bieżący rekord, nato-miast wywołanie ROLLBACK spowoduje za-mknięcie kursora i zwolnienie wszystkich

zasobów z nim związanych. Jeśli kursor ma klauzulę WITH HOLD, to po operacji COMMIT możesz wywołać jedynie polecenia FETCH lub CLOSE. Usuwanie lub modyfikowanie rekordu z użyciem kursora nie jest w ta-kim przypadku dozwolone, ponieważ po zatwierdzeniu transakcji wskaźnik kursora będzie ustawiony przed następnym rekor-dem, ale nie będzie już wskazywał na po-przednio przetwarzany rekord. Sprawdź-my w praktyce, jak działa klauzula WITH HOLD. Zapisz procedurę z Listingu 17 do pliku p16.sql i skompiluj poleceniem:

db2 -td@ -vf p16.sql

Następnie przygotuj początkowe dane wy-wołaniem:

db2 call clean()

i sprawdź zawartość tabeli stock polece-niem:

db2 select name,amount from stock

NAME AMOUNT

------------ -----------

orange 10

lemon 30

lime 60

grapes 90

apple 120

banana 150

Teraz uruchom procedurę hold _ test, wy-dając polecenie:

db2 call hold_test()

W wyniku działania tej procedury DB2 zgło-si błąd, do którego za moment wrócę, a tym-

Listing 21. Prosta obsługa błędu

-- p20.sql

CREATE PROCEDURE update_price(

IN p_name VARCHAR(12),

IN p_price DECFLOAT,

OUT p_result INTEGER

)

SPECIFIC p20

LANGUAGE SQL

BEGIN

DECLARE EXIT HANDLER FOR NOT FOUND --{1}

SET p_result = -1;

UPDATE stock SET price = p_price --{2}

WHERE name = p_name;

SET p_result = 0; --{3}

END

@

Listing 22. Handler i instrukcja RETURN

-- p21.sql

CREATE PROCEDURE update_price(

IN p_name VARCHAR(12),

IN p_price DECFLOAT

)

SPECIFIC p21

LANGUAGE SQL

BEGIN

DECLARE v_result INTEGER DEFAULT 0; --{1}

BEGIN

DECLARE EXIT HANDLER FOR NOT FOUND --{2}

SET v_result = -1;

UPDATE stock SET price = p_price --{3}

WHERE name = p_name;

END;

RETURN v_result; --{4}

END

@

Page 26: Sdj Extra 35 Db2

26

Tworzenie aplikacji

SDJ Extra 35

czasem sprawdź, jak zmieniła się zawartość tabeli, wołając ponownie:

db2 select name,amount from stock

NAME AMOUNT

------------ -----------

orange 1010

lemon 2030

lime 60

grapes 90

apple 120

banana 150

Wielkości stanów magazynowych dwóch pierwszych produktów powiększyły się odpowiednio o 1000 i 2000. Prześledź-my teraz, dlaczego tak się stało i dlacze-go serwer DB2 zgłosił błąd. Przeanalizuj-my treść procedury z Listingu 17. W wier-szu {1} zadeklarowałem kursor z klauzu-lą WITH HOLD. Następnie, po jego otwarciu, wywołałem instrukcję FETCH. Kursor usta-wił się na pierwszym rekordzie i pobrał ilość towaru do zmiennej v _ amount. W następnym kroku dodałem 1000 do aktu-alnej ilości towaru i w wierszu {3} zatwier-dziłem zmianę poleceniem COMMIT. Ponie-waż kursor ma klauzulę WITH HOLD, dlate-go nie został zamknięty i wymienione wy-żej czynności mogłem powtórzyć, by w wierszu {4} zatwierdzić powiększenie ilo-ści drugiego towaru o 2000. Po tej ope-racji wykonuję aktualizację ilości trzecie-go towaru o 3000, ale w wierszu {5} wy-wołuję ROLLBACK. To powoduje cofnięcie zmiany dokonanej na trzecim rekordzie i zamknięcie kursora. W efekcie, wykona-nie instrukcji FETCH w wierszu {6} koń-czy się niepowodzeniem, ponieważ kur-sor jest już zamknięty, a DB2 zgłasza na-stępujący błąd:

SQL0501N Kursor określony w instrukcji

FETCH lub CLOSE nie jest otwarty.

SQLSTATE=24501

W tej procedurze celowo nie zamykam kursora, ponieważ robi to instrukcja ROLLBACK. Jako ćwiczenie proponuję, abyś usunął klauzulę WITH HOLD z deklaracji kursora, ponownie skompilował procedu-rę i powtórzył cały scenariusz. Ile rekor-dów zostanie zmodyfikowanych? Oczy-wiście, tylko pierwszy. Pierwsze wywo-łanie COMMIT, zgodnie z naszymi oczeki-waniami, zamknie kursor i następująca po nim operacja FETCH zakończy się błę-dem. Zastosowanie klauzuli WITH HOLD może być szczególnie użyteczne w pro-cesach wsadowych, gdzie dane są pobie-rane za pomocą kursora, potem przetwa-rzane rekord po rekordzie, a każdy rekord jest przetwarzany w ramach jednej trans-akcji. Na koniec jeszcze jedna ciekawost-ka. W wierszu {2} procedury, w klauzu-li FOR UPDATE OF podałem nazwę kolum-ny amount. W ten sposób zadeklarowałem chęć zmiany za pomocą kursora tylko jed-nej, konkretnej kolumny. Gdybym w ko-

Listing 23. Obsługa specyficznego kodu błędu

-- p22.sql

CREATE PROCEDURE add_product(

IN p_name VARCHAR(12),

IN p_amount INTEGER,

IN p_price DECFLOAT

)

SPECIFIC p22

LANGUAGE SQL

BEGIN

DECLARE v_result INTEGER DEFAULT 0;

DECLARE cnd_upd CONDITION FOR SQLSTATE '23505'; --{1}

BEGIN

DECLARE CONTINUE HANDLER FOR cnd_upd --{2}

UPDATE stock

SET amount = p_amount, price = p_price

WHERE name = p_name;

INSERT INTO stock(name, amount, price) --{3}

VALUES (p_name, p_amount, p_price);

SELECT id INTO v_result FROM stock --{4}

WHERE name = p_name;

END;

RETURN v_result; --{5}

END

@

Listing 24. Zaawansowana obsługa błędów

-- p23.sql

CREATE PROCEDURE delete_product(

IN p_name VARCHAR(12),

OUT p_message VARCHAR(200)

)

SPECIFIC p23

LANGUAGE SQL

BEGIN

DECLARE v_amount INTEGER DEFAULT NULL;

DECLARE cnd_err CONDITION FOR SQLSTATE '99001'; --{1}

DECLARE EXIT HANDLER FOR cnd_err --{2}

GET DIAGNOSTICS EXCEPTION 1

p_message = DB2_TOKEN_STRING;

SET v_amount =

(SELECT amount FROM stock

WHERE name = p_name);

IF (v_amount IS NULL) THEN

SIGNAL cnd_err --{3}

SET MESSAGE_TEXT = 'Product not found';

ELSEIF (v_amount > 0) THEN

SIGNAL cnd_err --{4}

SET MESSAGE_TEXT = 'Product amount > 0';

ELSE

DELETE FROM stock WHERE name = p_name;

SET p_message = 'Deleted';

END IF;

END

@

Page 27: Sdj Extra 35 Db2

Procedury składowane w DB2 9

27www.sdjournal.org

dzie procedury w którejkolwiek instruk-cji UPDATE opartej na kursorze próbował zmodyfikować inną kolumnę niż amount, to otrzymałbym błąd już na etapie kompi-lacji procedury. Stosując takie rozwiąza-nie, możesz się zabezpieczyć przed przy-padkową modyfikacją kolumn.

Kursory – WITH RETURNDo tej pory wykorzystywaliśmy kursor do odczytu i modyfikacji danych. Za pomocą kursora możesz również przekazać określo-ny zestaw rekordów do procedury lub pro-gramu wywołującego. Aby zobaczyć, jak to zrobić, przyjrzyjmy się procedurze z Listin-gu 18. W wierszu {3} zadeklarowałem kur-sor z klauzulą WITH RETURN, co powodu-je, że otwierany w wierszu {4} kursor nie jest zamykany tuż przed wyjściem z pro-cedury, tylko pozostaje aktywny i dostęp-ny w procedurze lub programie wywołują-cym. Dodatkowo w wierszu {1} za pomocą instrukcji DYNAMIC RESULT SETS zadeklaro-wałem, że procedura zwraca jeden zestaw danych. W ten sposób program wywołują-cy, na przykład procesor poleceń db2, otrzy-muje zestaw danych, który wyświetla na konsoli. W podobny sposób jak na Listin-gu 18 możesz do programu lub procedu-ry wywołującej przekazać większą ilość ze-stawów danych. Chciałbym przy tej okazji zwrócić uwagę na fakt, że deklaracja kur-sora musi wystąpić po deklaracjach zmien-nych (o czym już wspominałem) oraz przed instrukcjami SELECT, UPDATE, DELETE oraz CREATE TABLE. Sposób, w jaki można sobie poradzić z tym ograniczeniem, pokazałem również na Listingu 18. Deklarację kurso-ra można umieścić w bloku BEGIN/END i w ten sposób może się ona znaleźć na końcu procedury, po innych instrukcjach SQL-owych. Wykorzystałem to rozwiązanie do napisania procedury, która najpierw po-dwaja ilość wszystkich towarów, a następ-nie za pomocą kursora zwraca nazwę i po-dwojoną ilość towaru do procedury wywo-łującej. Procedurę z Listingu 18 możesz skompilować poleceniem:

db2 -td@ -vf p17.sql

Ustaw początkowe dane wywołaniem:

db2 call clean()

i wywołaj kilkakrotnie procedurę double _

amounts:

db2 call double_amounts()

Za każdym razem ilość towarów jest po-dwajana i wyświetlana na konsoli. Po pierw-szym wywołaniu powinieneś uzyskać wynik przedstawiony w tabelce poniżej:

NAME AMOUNT

------------ -----------

orange 20

lemon 60

lime 120

grapes 180

apple 240

banana 300

Chciałbym jeszcze zwrócić uwagę na to, że klauzula WITH RETURN może wystąpić z do-datkowym modyfikatorem TO CALLER lub TO CLIENT. TO CALLER oznacza, że zbiór wy-nikowy oparty na kursorze będzie zwróco-

ny do procedury lub programu wywołują-cego, natomiast TO CLIENT oznacza, że zo-stanie on zwrócony do nadrzędnego pro-gramu wywołującego. Jeśli użyjesz klauzu-li WITH RETURN TO CLIENT i wywołasz proce-durę z innej procedury, to procedura wy-wołująca nie zobaczy zwracanego wyniku, zostanie on przekazany bezpośrednio do programu wywołującego.

Kursory – odbieranie zbioru wynikowegoWiemy już, jak przekazywać zestaw da-nych do procedury wywołującej. W kolej-

Listing 25. Użycie GET DIAGNOSTICS

-- p24.sql

CREATE PROCEDURE update_promo(

IN p_name VARCHAR(12),

IN p_promo SMALLINT,

OUT p_msg VARCHAR(300),

OUT p_count INTEGER,

OUT p_amount INTEGER

)

SPECIFIC p24

LANGUAGE SQL

BEGIN

DECLARE v_value DECFLOAT;

DECLARE EXIT HANDLER FOR NOT FOUND

GET DIAGNOSTICS EXCEPTION 1 --{1}

p_msg = MESSAGE_TEXT; --{2}

UPDATE stock SET promo = p_promo

WHERE name LIKE p_name;

GET DIAGNOSTICS p_count = ROW_COUNT; --{3}

SELECT SUM(amount) INTO v_value FROM stock

WHERE name LIKE p_name;

CALL calc_value(v_value,1.0,v_value,v_value);

GET DIAGNOSTICS p_amount = RETURN_STATUS; --{4}

END

@

Listing 26. Dynamiczny SQL – EXECUTE IMMEDIATE

-- p25.sql

CREATE PROCEDURE change_price(

IN p_value DECFLOAT,

IN p_condition VARCHAR(800),

OUT p_count INTEGER

)

SPECIFIC p25

LANGUAGE SQL

BEGIN

DECLARE v_sql VARCHAR(1000);

SET v_sql = 'UPDATE stock SET price = price + (' --{1}

|| CHAR(p_value)

|| ') WHERE '

|| p_condition;

EXECUTE IMMEDIATE v_sql; --{2}

GET DIAGNOSTICS p_count = ROW_COUNT; --{3}

END

@

Page 28: Sdj Extra 35 Db2

28

Tworzenie aplikacji

SDJ Extra 35

nym przykładzie pokażę, jak w procedu-rze wywołującej odebrać zestaw danych. W tym celu przygotowałem procedurę quad_amounts przedstawioną na Listin-gu 19, która zwiększa ilość towaru czte-rokrotnie i wykorzystuje do tego celu po-dwojone wartości zwrócone przez proce-

durę double_amounts. Aby wykorzystać zestaw danych przekazany z procedury wywoływanej, trzeba wykonać dwie czyn-ności. Najpierw należy powiązać tak zwa-ny lokator (wskaźnik) z procedurą wywo-ływaną i zwracanym przez nią zestawem danych za pomocą instrukcji ASSOCIATE

RESULT SET LOCATOR. Następnie trzeba za-alokować kursor poleceniem ALLOCATE CURSOR. Spójrzmy na przykład z Listingu 19. W wierszu {1} zadeklarowałem zmien-ną będącą lokatorem zestawu danych o na-zwie rs_amounts. Następnie w wierszu {2} wywołuję metodę double_amounts, która zwraca jeden zestaw danych. Dopiero po wywołaniu tej metody, w wierszu {3} mogę powiązać lokator rs_amounts z zestawem danych zwróconym przez tę procedurę. W wierszu {4} alokuję kursor c_amounts, któ-ry będzie oparty na lokatorze rs_amounts, czyli będzie wskazywał zestaw danych zwrócony przez double_amounts. Tak za-alokowany kursor wykorzystuję do dal-szych obliczeń w podobny sposób jak kur-sor zadeklarowany jawnie. Przed użyciem zaalokowany kursor nie musi być otwiera-ny instrukcją OPEN. Do zwiększenia ilości produktu wykorzystuję pętlę WHILE i po-lecenie FETCH do iterowania po wszystkich rekordach, a do modyfikacji wykorzystu-ję w wierszu {5} instrukcję UPDATE. Zwróć uwagę, że w klauzuli WHERE nie można wy-korzystywać kursora utworzonego instruk-cją ALLOCATE CURSOR, dlatego jawnie musia-łem podać warunek wyszukiwania towaru po nazwie. W wierszu {6} zamykam kur-sor poleceniem CLOSE, co powoduje zwol-nienie wszystkich zasobów i zamknięcie zestawu danych zwróconego przez me-todę double_amounts. W wierszu {7} za-stosowałem trik z deklarowaniem kursora na końcu procedury w bloku BEGIN..END, aby zwrócić zmodyfikowaną listę towa-rów. Sprawdźmy zatem procedurę quad_amounts w działaniu. W tym celu skompi-luj ją poleceniem:

db2 -td@ -vf p18.sql

Następnie wyczyść tabelę testową, wołając:

db2 call clean()

i wywołaj kilkakrotnie procedurę quad _

amounts:

> db2 call quad_amounts()

Po pierwszym wywołaniu, ilości towarów powinny być takie jak w tabeli poniżej, no i oczywiście po każdym kolejnym powinny zwiększać się czterokrotnie.

NAME AMOUNT

------------ -----------

orange 40

lemon 120

lime 240

grapes 360

apple 480

banana 600

Listing 27. Dynamiczny SQL – PREPARE/EXECUTE

-- p26.sql

CREATE PROCEDURE add(

IN p_names NAME_ARRAY,

IN p_amounts AMOUNT_ARRAY,

IN p_prices PRICE_ARRAY

)

SPECIFIC p26

LANGUAGE SQL

BEGIN

DECLARE v_name VARCHAR(30);

DECLARE v_amount INTEGER;

DECLARE v_price DECIMAL(8,2);

DECLARE n,i INTEGER;

DECLARE v_sql VARCHAR(1000);

DECLARE v_stmt STATEMENT; --{1}

SET v_sql = 'INSERT INTO stock (name,amount,price)

VALUES (?,?,?)'; --{2}

PREPARE v_stmt FROM v_sql; --{3}

SET n = CARDINALITY(p_names);

SET i = 1;

WHILE (i <= n) DO

VALUES (p_names[i],p_amounts[i],p_prices[i]) --{4}

INTO v_name,v_amount,v_price;

EXECUTE v_stmt USING v_name,v_amount,v_price; --{5}

SET i = i + 1;

END WHILE;

END

@

Listing 28. Dynamiczne kursory

-- p27.sql

CREATE PROCEDURE list(

IN p_columns VARCHAR(200),

IN p_condition VARCHAR(800)

)

SPECIFIC p27

DYNAMIC RESULT SETS 1

LANGUAGE SQL

BEGIN

DECLARE v_sql VARCHAR(1000);

DECLARE v_stmt STATEMENT; --{1}

DECLARE c_stock CURSOR WITH RETURN FOR v_stmt; --{2}

SET v_sql = 'SELECT ' --{3}

|| p_columns

|| ' FROM stock WHERE '

|| p_condition;

PREPARE v_stmt FROM v_sql; --{4}

OPEN c_stock; --{5}

END

@

Page 29: Sdj Extra 35 Db2

Procedury składowane w DB2 9

29www.sdjournal.org

Może wydawać się, że zamiast przekazy-wania zbiorów wynikowych lepiej jest od razu całość pracy wykonać w nadrzędnej procedurze. Warto jednak zwrócić uwa-gę, że procedury mogą być uruchamia-ne z różnymi poziomami uprawnień. Bar-dzo często odbiera się użytkownikom bez-pośredni dostęp do tabel, natomiast udo-stępnia się prawo wykonywania procedur i odbierania zwracanych przez nich zbio-rów wynikowych. Zbiory wynikowe mo-gą być także odebrane przez aplikację na-pisaną w innym języku programowania, np. w Java czy C. Dodatkowo zbiór wyni-kowy może być zbudowany w oparciu o ta-belę tymczasową, której zawartość może zostać utworzona na podstawie wielu in-nych tabel.

Podstawowa obsługa błędówPodczas tworzenia i uruchamiania procedur składowanych z pewnością spotkasz się z sy-tuacją, kiedy jedna z instrukcji SQL-owych z jakiegoś powodu nie wykona się poprawnie, a wtedy Twoja procedura zostanie przerwa-na i zakończy swoje działanie. DB2 dostar-cza kilka mechanizmów pomocnych w ob-słudze takich sytuacji, dzięki czemu możesz na nie odpowiednio zareagować. Na kolej-nych przykładach pokażę, jak sprawdzić, czy instrukcja SQL-owa wykonała się popraw-nie i jak poradzić sobie z ewentualnymi błę-dami. Serwer DB2 po wykonaniu każdej in-strukcji SQL-owej, ustawia dwie zmienne o nazwach SQLSTATE i SQLCODE. Możesz je wy-korzystać do sprawdzenia, czy określona in-strukcja wykonała się poprawnie. Zmienna SQLSTATE przechowuje pięcioznakowy kod zgodny z ISO/ANSI SQL92, którego warto-ści mają następujące znaczenie:

• ‘00xxx' instrukcja wykonała się popraw-nie;

• ‘01xxx' podczas wykonania pojawiło się ostrzeżenie;

• ‘02xxx' podczas wykonania wystąpił błąd.

Znaczenie wartości zmiennej SQLSTATE jest takie samo dla całej rodziny produk-tów DB2, jak i baz danych innych produ-centów, ale dostarcza dość ogólnych in-formacji na temat sytuacji wyjątkowych. Bardziej szczegółowe informacje moż-na uzyskać, analizując wartości zmiennej SQLCODE, która zwraca kod błędu w posta-ci liczby całkowitej. Znaczenie SQLCODE jest specyficzne dla serwera DB2 i może się różnić pomiędzy poszczególnymi wer-sjami. Ogólnie znaczenie SQLCODE jest na-stępujące:

• 0 – poprawne wykonanie instrukcji;• >0 – pojawiło się ostrzeżenie;

• 100 – pojawiło się ostrzeżenie typu NOT FOUND;

• <0 – wystąpił inny błąd.

Dla przykładu, jeśli spróbujesz instrukcją UPDATE zmodyfikować rekord, który nie ist-nieje, to otrzymasz komunikat:

SQL0100W Nie znaleziono wiersza dla

FETCH, UPDATE lub DELETE albo

rezultatem zapytania jest pusta

tabela. SQLSTATE=02000

Wykorzystując procesor poleceń, możesz do-wiedzieć się więcej o kodzie błędu, wywołu-jąc dla SQLCODE polecenie:

db2 ? SQL0100W

a dla SQLSTATE polecenie:

db2 ? 02000

Na Listingu 20 przedstawiłem procedu-rę, w której zachodzi taki przypadek. Mo-że się zdarzyć, że w wierszu {3} będzie modyfikowany rekord, który nie istnie-je, ponieważ w parametrze p _ name po-

dana została nazwa produktu, którego nie ma w tabeli stock. W takim przypad-ku instrukcja UPDATE zwróci kody błędów w zmiennych SQLSTATE i SQLCODE, któ-re odczytuję w wierszu {4}, zapisując je do parametrów wyjściowych p _ state i p _ code. Zauważ, że zmienne SQLSTATE i SQLCODE wcześniej zadeklarowałem w wierszach {1} i {2} i celowo nadałem im wartości odpowiednio ‘99999' oraz 99999, aby pokazać, że obie zmienne są modyfi-kowane przez DB2 zarówno po popraw-nym, jak i błędnym wykonaniu instruk-cji UPDATE. Czas sprawdzić działanie tej procedury w praktyce. Zapisz procedurę z Listingu 20 do pliku p19.sql i skompi-luj poleceniem:

db2 -td@ -vf p19.sql

Ustaw dane początkowe w tabeli stock po-leceniem:

db2 call clean()

Następnie uruchom procedurę, wywołując:

db2 call update_price('orange',80.0,?,?)

Ramka 1. Przekazywanie i odbieranie parametrów w procedurach dynamicznych

USING v_row.amount v_row.price p_out

parametr p_amount p_price p_value p_total

typ IN IN OUT INOUT

INTO v_value p_out

Listing 29. Dynamiczne wywoływanie procedur

-- p28.sql

CREATE PROCEDURE calculate_totals(

OUT p_out DECFLOAT

)

SPECIFIC p28

LANGUAGE SQL

BEGIN

DECLARE v_value DECFLOAT;

DECLARE v_sql VARCHAR(80);

DECLARE v_stmt STATEMENT; --{1}

SET v_sql = 'CALL calc_value(?,?,?,?)'; --{2}

PREPARE v_stmt FROM v_sql; --{3}

SET p_out = 0.0;

FOR v_row AS SELECT amount,price FROM stock

DO

EXECUTE v_stmt --{4}

INTO v_value,p_out --{5}

USING v_row.amount,v_row.price,p_out; --{6}

END FOR;

END

@

Page 30: Sdj Extra 35 Db2

30

Tworzenie aplikacji

SDJ Extra 35

Procedura powinna zwrócić komunikat:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_STATE

Wartość parametru: 00000

Nazwa parametru: P_CODE

Wartość parametru: 0

Instrukcja UPDATE zakończyła się popraw-nie i cena jednostkowa pomarańczy zosta-ła zmieniona na wartość 80.0, co możesz sprawdzić poleceniem:

db2 select * from stock

Ponownie uruchom procedurę update _

price, ale tym razem podaj nazwę produktu, którego nie ma na magazynie, na przykład:

db2 call update_price('melon',80.0,?,?)

Wynik działania procedury powinien być następujący:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_STATE

Wartość parametru: 02000

Nazwa parametru: P_CODE

Wartość parametru: 100

Instrukcja UPDATE spowodowała ustawie-nie odpowiednio 02000 dla SQLSTATE i

+100 dla SQLCODE, co oznacza, że rekord nie został znaleziony. Przedstawiony spo-sób sprawdzania błędu wykonania, jak-kolwiek poprawny, może być nieprak-tyczny, jeśli procedura będzie nieco bar-dziej skomplikowana, a instrukcji SQL-owych będzie kilkadziesiąt. Dlatego też w DB2 dostępny jest mechanizm obsłu-gi błędów i wyjątków za pomocą tak zwa-nych handlerów. Handler jest to wyraże-nie SQL-owe, które zostanie wywołane w przypadku wystąpienia błędu lub wy-jątku. Handlery mogą być deklarowane w obrębie instrukcji blokowej i taki jest ich zakres działania. Handlery są trzech ty-pów: EXIT, CONTINUE i UNDO. W handle-rze typu EXIT wykonane zostaną wszyst-kie jego instrukcje i nastąpi wyjście z blo-ku, w którym został zadeklarowany, a jeśli taki handler jest zadeklarowany dla pro-cedury (w nadrzędnym bloku), to nastą-pi wyjście z całej procedury. Handler ty-pu CONTINUE wykona wszystkie swoje in-strukcje i powróci do instrukcji znajdują-cej się zaraz po poleceniu, które spowo-dowało błąd lub wyjątek. Handler typu UNDO działa podobnie jak EXIT, z tym że wszystkie instrukcje, które się poprawnie wykonały w danym bloku, zostaną wyco-fane, ale blok ten musi być zadeklarowa-

ny z klauzulą ATOMIC. Handlery definiuje się dla określonych typów zdarzeń. Istnie-ją trzy predefiniowane ogólne typy zda-rzeń, są to: SQLEXCEPTION, SQLWARNING i NOT FOUND. Handler może być również za-deklarowany dla konkretnego typu zda-rzenia o podanym kodzie SQLSTATE. Prze-śledźmy prosty przykład użycia handlera typu EXIT dla zdarzenia typu NOT FOUND, który został przedstawiony na Listingu 21. Jeśli w parametrze p _ name do proce-dury zostanie przekazana nazwa nieist-niejącego produktu, to instrukcja UPDATE w wierszu {2} zgłosi ostrzeżenie o kodzie +100, czyli NOT FOUND. Ponieważ w proce-durze w wierszu {1} jest zadeklarowany handler dla zdarzenia NOT FOUND, to ste-rowanie zostanie przekazane do tego han-dlera. W handlerze jest zdefiniowana tyl-ko jedna instrukcja, zostanie ona wykona-na i nastąpi zakończenie działania proce-dury. Tyle teorii, teraz sprawdźmy proce-durę w działaniu. Zapisz kod procedury z Listingu 21 do pliku o nazwie p20.sql i skompiluj ją poleceniem:

db2 -td@ -vf p20.sql

Ustaw dane początkowe wywołaniem:

db2 call clean()

a następnie uruchom:

db2 call update_price('lemon',25.0,?)

Procedura powinna zwrócić komunikat:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_RESULT

Wartość parametru: 0

a cena cytryn powinna zostać zmieniona na wartość 25.0. Wywołaj jeszcze raz tę proce-durę, ale podaj nazwę nieistniejącego pro-duktu:

db2 call update_price('melon',25.0,?)

Cena żadnego towaru nie uległa zmianie, na-tomiast procedura zwróciła komunikat:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_RESULT

Wartość parametru: -1

Zauważ, że rezultat wykonania procedu-ry jest przekazywany w parametrze wyj-ściowym p _ result i o ile w poprzednim wywołaniu miał wartość 0, teraz ma war-tość -1. Oznacza to, że w przypadku błędu instrukcja w wierszu {3} nie wykonała się,

a zamiast niej wywołany został handler w wierszu {1}. Do tej pory komunikaty błę-dów przekazywaliśmy do programu wy-wołującego za pomocą parametrów wyj-ściowych. Status wykonania danej opera-cji możemy przekazać także za pomocą in-strukcji RETURN. Kłopot jednak polega na tym, że w handlerze nie można użyć po-lecenia RETURN (dotyczy wersji DB2 9.5 i wcześniejszych; od wersji DB2 9.7 można już korzystać z instrukcji RETURN w han-dlerze). Aby obejść to ograniczenie, mu-simy zadeklarować handler w bloku za-gnieżdżonym BEGIN..END, tak jak to po-kazałem na Listingu 22. Jest to procedu-ra z poprzedniego przykładu, ale rezultat wykonania przechowywany jest w zmien-nej lokalnej zadeklarowanej w wierszu {1} z wartością domyślną 0. Jeśli w instruk-cji UPDATE w wierszu {3} wystąpi błąd NOT FOUND, to sterowanie zostanie przekazane do handlera w wierszu {2}, zmiennej v _

result zostanie przypisana wartość -1, a ponieważ handler jest typu EXIT, to na-stępną wykonywaną instrukcją będzie po-lecenie w wierszu {4}. Sprawdźmy działa-nie tej procedury. Zapisz kod procedury z Listingu 22 do pliku p21.sql i skompiluj poleceniem:

db2 -td@ -vf p21.sql

Ustaw dane początkowe, wołając:

db2 call clean()

a następnie uruchom procedurę, wywołując:

db2 call update_price('lemon',25.0)

Procedura powinna zwrócić komunikat:

Status powrotu= 0

Cena cytryn zostanie ustawiona na wartość 25.0, a procedura zwróci wartość 0. Uru-chom tę procedurę jeszcze raz z nazwą pro-duktu, którego nie ma w tabeli stock, na przykład:

db2 call update_price('melon',25.0)

W wyniku pojawienia się błędu NOT FOUND procedura jako rezultat zwróci wartość -1.

W pracy przy systemach rejestracyjnych często spotykamy się z sytuacją, kiedy chce-my dodać nowy rekord, ale okazuje się, że rekord o podanym kluczu już istnieje i wte-dy chcielibyśmy ten rekord tylko zaktuali-zować. Na Listingu 23 przedstawiłem pro-cedurę, która do rozwiązania tego proble-mu wykorzystuje handler typu CONTINUE dla specyficznego kodu błędu SQLSTATE o wartości ‘23505' (naruszenie więzów inte-

Page 31: Sdj Extra 35 Db2

Procedury składowane w DB2 9

31www.sdjournal.org

gralności). Jeśli do procedury add_product przekazana zostanie nazwa nowego pro-duktu, to zostanie on dodany do tabeli wraz z ilością i ceną. Jeśli jednak podana nazwa produktu już istnieje w tabeli stock, to w wierszu {3} instrukcja INSERT zakoń-czy się błędem o kodzie ‘23505' i sterowa-nie zostanie przekazane do handlera zade-klarowanego w wierszu {1}. W kodzie han-dlera znajduje się instrukcja UPDATE, któ-ra zaktualizuje ilość i cenę dla już istnie-jącego produktu. Ponieważ jest to handler typu CONTINUE, to sterowanie powróci do wiersza {4}, w którym do zmiennej lokal-nej jest pobierany identyfikator modyfiko-wanego produktu. W wierszu {5} identyfi-kator produktu jest zwracany jako rezultat wykonania procedury. Przykład z Listingu 23 pokazuje, jak wykorzystać handler ty-pu CONTINUE i jak zadeklarować handler dla specyficznego kodu błędu, jednak w prak-tyce nie zalecałbym stosowania handle-rów do obsługi sytuacji przedstawionej w tym przykładzie, ponieważ obsługa błędów jest dosyć kosztowna i częste wywoływanie handlera może mieć negatywny wpływ na wydajność aplikacji. W tym szczególnym przypadku taniej jest sprawdzić, czy pro-dukt o podanej nazwie już istnieje i w za-leżności od tego wywołać albo instrukcję INSERT, albo UPDATE, bądź wykorzystać go-tową instrukcję MERGE. Sprawdźmy zatem działanie procedury z Listingu 23. Zapisz jej treść w pliku p22.sql i skompiluj, wy-wołując:

db2 -td@ -vf p22.sql

Ustaw dane początkowe w tabeli stock wy-wołaniem:

db2 call clean()

a następnie dodaj nowy produkt, urucha-miając procedurę poleceniem:

db2 call add_product('melon',100,12.50)

W wyniku działania tej procedury do tabeli stock został dodany nowy produkt w ilości 100 i cenie jednostkowej 12.50, co możesz sprawdzić, wywołując:

db2 select * from stock

Identyfikator produktu powinien być taki sam jak wartość zwrócona przez procedurę – w moim przypadku jest to 7:

Status powrotu= 7

Wywołaj ponownie tę samą procedurę, zmieniając tylko cenę jednostkową, na przy-kład:

db2 call add_product('melon',100,8.39)

Procedura nie dodała nowego rekordu, tylko zmodyfikowała cenę jednostkową produktu, który już istnieje w tabeli, zwracając identy-fikator modyfikowanego rekordu.

Zaawansowana obsługa błędówW poprzednich przykładach używałem handlerów dla predefiniowanych kodów błędów, a ich wywoływanie odbywało się automatycznie po wystąpieniu określonego błędu. W SQL PL istnieje możliwość defi-niowania własnych kodów błędów i „ręczne-go” wywołania określonego handlera. Wła-sne kody błędów deklaruje się za pomocą in-strukcji DECLARE CONDITION, natomiast wy-wołanie określonego handlera można wy-musić poleceniem SIGNAL. Na Listingu 24 przedstawiłem procedurę, w której wyko-rzystałem własny kod błędu do zasygnali-zowania braku możliwości usunięcia okre-ślonego produktu. W wierszu {1} zadekla-rowałem kod błędu o wartości ‘99001', tak aby nie pokrywał się z predefiniowanymi w DB2 wartościami SQLSTATE, a w wierszu {2} zdefiniowałem dla niego handler typu EXIT. Jeśli produkt o podanej nazwie nie istnieje, to w wierszu {3} zgłaszam błąd poleceniem SIGNAL, podając nazwę błędu i dodatkowy komunikat informacyjny. W wierszu {4} również zgłaszam błąd, jeśli ilość produk-tu na magazynie jest większa od zera. Wy-wołanie polecenia SIGNAL w wierszach {3} i {4} powoduje wywołanie handlera z wier-sza {2}, wewnątrz którego ustawiany jest pa-rametr wyjściowy z komunikatem błędu. Do pobrania komunikatu błędu użyłem w handlerze instrukcji GET DIAGNOSTICS, któ-rą szczegółowo omówię na następnym przy-kładzie. Sprawdźmy teraz, jak w praktyce działa procedura delete_product dla róż-nych przypadków. Zapisz kod procedury z Listingu 24 do pliku p23.sql i skompiluj poleceniem:

db2 -td@ -vf p23.sql

Ustaw dane początkowe, wywołując pole-cenia:

db2 call clean()

db2 update stock set amount = 0 where name

= 'orange'

Teraz wywołaj procedurę delete _ product dla istniejącego produktu, którego ilość jest większa od zera, na przykład:

db2 call delete_product('lemon',?)

Wybrany produkt nie zostanie usunięty, a procedura zwróci komunikat informujący o tym, że jego ilość jest większa od zera:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_MESSAGE

Wartość parametru: Product amount > 0

Ponownie wywołaj tę procedurę dla produk-tu, którego ilość na magazynie jest zerowa:

db2 call delete_product('orange',?)

Produkt zostanie usunięty z tabeli stock, a procedura zwróci komunikat z informacją o jego usunięciu:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_MESSAGE

Wartość parametru: Deleted

Na koniec uruchom procedurę dla produk-tu, który nie istnieje, na przykład:

db2 call delete_product('watermelon',?)

Procedura zwróci jedynie komunikat infor-mujący o tym, że produkt nie został znale-ziony:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_MESSAGE

Wartość parametru: Product not found

Przy tej okazji chciałbym jeszcze wspo-mnieć o instrukcji GET DIAGNOSTICS, uży-tej w przykładzie z Listingu 24. Instruk-cja ta daje dostęp do komunikatów błędów w obrębie handlera oraz pozwala spraw-dzić ilość rekordów zmodyfikowanych w ostatniej instrukcji SQL-owej oraz war-tość zwracaną przez wywoływaną proce-durę. W przykładzie z Listingu 24 w wier-szu {2} użyłem instrukcji GET DIAGNOSTICS do pobrania treści komunikatu ustawio-nej w poleceniu SIGNAL w wierszach {3} i {4}. Treść tego komunikatu jest ustawia-na przez DB2 w zmiennej o nazwie DB2 _

TOKEN _ STRING. Może to być trochę my-lące, ponieważ w instrukcji SIGNAL usta-wiam zmienną MESSAGE _ TEXT, więc mógłbym się spodziewać, że w handle-rze w GET DIAGNOSTICS EXCEPTION zmien-na MESSAGE _ TEXT ma dokładnie tę samą wartość. Okazuje się jednak, że tekst z po-lecenia SIGNAL trafia do zmiennej DB2 _

TOKEN _ STRING. Z kolei w przykładzie z Listingu 25 w wierszu {2} użyłem instruk-cji GET DIAGNOSTICS do pobrania pełnego komunikatu błędu ustawianego w zmien-nej MESSAGE _ TEXT przez serwer DB2. Do sprawdzenia ilości rekordów zmodyfiko-wanych przez ostatnią instrukcję INSERT/UPDATE trzeba użyć w poleceniu GET DIAGNOSTICS zmiennej ROW _ COUNT, tak jak

Page 32: Sdj Extra 35 Db2

32

Tworzenie aplikacji

SDJ Extra 35

w wierszu {3} na Listingu 25, a do spraw-dzenia wartości zwracanej przez procedu-rę musisz użyć zmiennej RETURN _ STATUS, tak jak to pokazałem w wierszu {4}. Zapisz procedurę z Listingu 25 do pliku p24.sql i skompiluj poleceniem:

db2 -td@ -vf p24.sql

Ustaw dane początkowe, wywołując:

db2 call clean()

a następnie wywołaj tę procedurę z parame-trami jak poniżej:

db2 call update_promo('l%',3,?,?,?)

W efekcie jej działania kody promocyjne produktów, których nazwa zaczyna się na literę ‘l’, zostaną ustawione na wartość 3, a ponadto procedura zwróci następujące war-tości:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_MSG

Wartość parametru: -

Nazwa parametru: P_COUNT

Wartość parametru: 2

Nazwa parametru: P_AMOUNT

Wartość parametru: 90

Pusty komunikat błędu w parametrze P _

MSG oznacza, że procedura zakończyła się po-prawnie, parametr P _ COUNT zawiera liczbę zmodyfikowanych rekordów, a P _ AMOUNT zawiera sumę ilości tych produktów, któ-rych kod promocyjny został zmieniony. Jeśli wywołasz tę samą procedurę dla produktów, które nie istnieją, na przykład:

db2 call update_promo('x%',3,?,?,?)

to procedura zwróci komunikat błędu, a po-zostałe parametry nie będą ustawione:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_MSG

Wartość parametru: SQL0100W Nie

znaleziono wiersza dla FETCH,

UPDATE lub DELETE albo rezultatem

zapytania jest pusta tabela.

Nazwa parametru: P_COUNT

Wartość parametru: -

Nazwa parametru: P_AMOUNT

Wartość parametru: -

Status powrotu= 0

Dynamiczny SQLDo tej pory we wszystkich przykładach wy-korzystywaliśmy statyczny SQL, choć jaw-nie o tym nie mówiliśmy. W tym rozdzia-

le przedstawię, jak w dynamiczny sposób tworzyć i wywoływać zapytania SQL-owe. Na wstępie chciałbym wyjaśnić, że każde zapytanie SQL-owe, zarówno statyczne, jak i dynamiczne musi przejść przez dwie fazy, zwane prepare i execute. W fazie prepa-re silnik bazy danych weryfikuje popraw-ność składni zapytania, sprawdza, czy za-pytanie może być w jakiś sposób zopty-malizowane, szuka najlepszej kombina-cji indeksów do użycia w zapytaniu oraz optymalizuje kolejność złączeń tabel. W fazie execute następuje wykonanie zapyta-nia i pobranie danych. Jak się domyślasz, w statycznym SQL-u faza prepare jest wy-konywana tylko raz, a wszystkie uzyska-ne w tej fazie informacje są trwale zapi-sywane w bazie danych. Dla dynamiczne-go SQL-a faza prepare jest wykonywana za każdym razem, a w celu poprawienia wy-dajności, dla często wykonywanych zapy-tań dynamicznych, efekty działania fazy prepare są przechowywane w dynamicz-nym cache’u (tzw. package cache). Użycie dynamicznego SQL-a wprowadza dodatko-wy narzut związany z kompilacją w trakcie wykonywania zapytania, ale za to daje nam możliwości, których nie uzyskamy, stosu-jąc statyczny SQL, w szczególności w sy-tuacjach, kiedy aż do momentu wykona-nia zapytania nie wiemy, jaki będzie jego końcowy kształt. Taki przypadek przedsta-wiłem na Listingu 26. Procedura change_price umożliwia zwiększenie lub zmniej-szenie ceny dowolnych produktów o do-wolną kwotę. Zapytanie SQL-owe jest dy-namicznie tworzone w wierszu {1} na pod-stawie parametrów przekazanych do pro-cedury (kwota oraz warunek selekcji rekor-dów). Zapytanie jest zapisywane w zmien-nej tekstowej, a następnie wykonywane in-strukcją EXECUTE IMMEDIATE w wierszu {2}. Instrukcja ta wykonuje obie fazy zapytania SQL-owego, czyli prepare i execute. Po wy-konaniu zapytania w wierszu {3} spraw-dzam, ile rekordów zostało zmienionych, a wynik zapisuję w parametrze wyjściowym. Przekonajmy się zatem, jak działa ta pro-cedura. Zapisz jej treść w pliku p25.sql i skompiluj poleceniem:

db2 -td@ -vf p25.sql

Ustaw dane początkowe poleceniem:

db2 call clean()

Spróbujmy teraz zwiększyć cenę wszystkich produktów o 2.50, wywołując:

db2 call change_price(2.50,'name like

''%''',?)

Procedura zwróci następujący rezultat:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_COUNT

Wartość parametru: 6

Co oznacza, że zmodyfikowanych zostało sześć rekordów – ich ceny możesz sprawdzić poleceniem:

db2 select * from stock

W procedurze change _ price wykorzy-stałem instrukcję EXECUTE IMMEDIATE do wykonania dynamicznego zapytania, któ-ra, jak już wspomniałem, uruchamia obie fazy zapytania SQL-owego: prepare i exe-cute. W praktyce, stosując dynamiczny SQL, często zdarza się sytuacja, kiedy fa-zę prepare można wykonać tylko raz, a na-stępnie wielokrotnie wykonuje się fazę execute. Rozwiązanie takie jest optymal-ne, ponieważ nie powtarza się kosztow-nej czasowo fazy prepare. DB2 udostępnia dwie instrukcje do wykonania faz prepa-re i execute, które nazywają się nie inaczej jak właśnie PREPARE i EXECUTE. W proce-durze na Listingu 27 wykorzystałem po-lecenie PREPARE do przygotowania dyna-micznego zapytania, a potem wielokrot-nie wywołuję polecenie EXECUTE. Proce-dura add z Listingu 27 dodaje do tabeli rekordy na podstawie przekazanych pa-rametrów, a przy tej okazji chciałem rów-nież pokazać, jak w procedurach składo-wanych możesz użyć typów tablicowych. Prześledźmy zatem treść tej procedury. Procedura przyjmuje jako parametry trzy typy tablicowe: tablicę nazw produktów p _ names, tablicę ilości produktów p _

amounts oraz tablicę cen produktów p _

prices. Te trzy tablice muszą mieć ten sam rozmiar, inaczej procedura zakoń-czy się błędem. W wierszu {1} zadeklaro-wałem zmienną typu STATEMENT, w której zostanie zapisane przygotowywane zapy-tanie. W wierszu {3} instrukcją PREPARE przygotowuję dynamiczne zapytanie na podstawie tekstu zapisanego w zmiennej tekstowej w wierszu {2}. Następnie w pę-tli – tyle razy, ile elementów zawierają ta-blice (wszystkie mają ten sam rozmiar) – uruchamiam zapytanie SQL-owe, wyko-nując instrukcję EXECUTE w wierszu {5}. Zauważ, że w treści dynamicznego zapy-tania, w wierszu {2} w klauzuli VALUES, umieszczone są znaki zapytania. W ich miejsce podczas wywołania instrukcji EXECUTE w wierszu {5} zostaną podstawio-ne wartości zmiennych dokładnie w takiej kolejności w jakiej występują w klauzu-li USING. Ze względu na to, że w klauzuli USING nie mogą wystąpić tablice, w wier-szu {4} musiałem przepisać wartości z ta-blic do odpowiednich zmiennych. Podsu-

Page 33: Sdj Extra 35 Db2

Procedury składowane w DB2 9

33www.sdjournal.org

mowując, procedura add tylko raz przygo-towuje zapytanie tworzone dynamicznie, które następnie wykonuje tyle razy, ile elementów zostało przekazanych w tabli-cach. Sprawdźmy zatem działanie tej pro-cedury w praktyce. Zapisz treść procedu-ry z Listingu 27 do pliku p26.sql i skom-piluj poleceniem:

db2 -td@ -vf p26.sql

Ustaw dane początkowe, wywołując:

db2 call clean()

Teraz dodaj nowe trzy produkty, wołając:

db2 call add(ARRAY['a', 'b', 'c'],

ARRAY[1, 2, 4], ARRAY[1.0, 2.0,

3.0])

Sprawdź zawartość tabeli stock poleceniem:

db2 select * from stock

Oprócz dynamicznych zapytań typu SELECT, INSERT lub UPDATE, w procedu-rach składowanych możesz również two-rzyć dynamiczne kursory, których zawar-tość będzie oparta na zapytaniu tworzo-nym tuż przed otwarciem kursora. Na Li-stingu 28 przedstawiłem procedurę, któ-ra zwraca zestaw danych oparty na kur-sorze, ale liczba kolumn i lista zwróco-nych rekordów zależą od przekazanych do procedury parametrów. W parame-trze p _ columns przekazuję listę nazw kolumn do wyświetlenia, a w parametrze p _ condition warunek selekcji rekor-dów. Przykład ten pokazuje, jak za pomo-cą dynamicznego kursora w prosty sposób można zrealizować funkcjonalność dy-namicznego raportu. Przyjrzyjmy się za-tem bliżej tej procedurze. W wierszu {1} zadeklarowałem zmienną do przechowa-nia przygotowanego zapytania, a w wier-szu {2} zadeklarowałem kursor oparty na tej zmiennej. Treść dynamicznego zapy-tania zapisuję w wierszu {3} do zmiennej tekstowej, a w wierszu {4} przygotowuję zapytanie, wywołując polecenie PREPARE. Następnie w wierszu {5} otwieram kur-sor, a ponieważ jest on zadeklarowany z klauzulą WITH RETURN, to zostanie zwróco-ny do programu wywołującego. Dzięki te-mu rezultat działania procedury list mo-żemy zobaczyć bezpośrednio na konsoli procesora poleceń. Skompiluj zatem pro-cedurę z Listingu 28, zapisując jej treść w pliku p27.sql:

db2 -td@ -vf p27.sql

Ustaw dane początkowe, wywołując:

db2 call clean()

Uruchom procedurę poleceniem:

db2 call list('name,price','price ">="

4.00')

Na ekranie zostaną wyświetlone nazwy i ce-ny wszystkich produktów, których cena jed-nostkowa jest większa lub równa 4.00:

Tabela wynikowa 1

--------------

NAME PRICE

------------ ------------------

grapes 4.20

apple 5.25

banana 6.30

Wybrano 3 rekordów.

Omówiliśmy już dynamiczne instrukcje, dynamiczne kursory, pozostał nam do omówienia problem dynamicznego wywo-ływania procedur. W zasadzie dynamicz-ne wywołanie procedury nie różni się ni-czym od wywołania na przykład instruk-cji SELECT, z tym zastrzeżeniem, że wywo-ływana procedura może przyjmować para-metry wejściowe, wyjściowe i wejściowo-wyjściowe. Dlatego też składnia polecenia EXECUTE dla wywoływanych procedur za-wiera klauzule INTO oraz USING. W klau-zuli INTO podajemy zmienne, do których zostaną zapisane wartości z parametrów wyjściowych i wejściowo-wyjściowych, a w klauzuli USING podajemy zmienne za-wierające parametry wejściowe wywoły-wanej procedury. Na Listingu 29 zamie-ściłem procedurę, która oblicza całkowi-tą wartość wszystkich produktów, a do obliczeń wykorzystuje procedurę calc _

value z Listingu 4. W wierszu {1} zadekla-rowałem zmienną do przechowania uru-chamianego zapytania. W wierszu {2} za-deklarowałem treść dynamicznego wy-wołania procedury calc _ value, z ozna-czeniem parametrów za pomocą czterech znaków zapytania. Dla przypomnienia, deklaracja procedury calc _ value wyglą-da następująco:

CREATE PROCEDURE calc_value(

IN p_amount INTEGER,

IN p_price DECFLOAT,

OUT p_value DECFLOAT,

INOUT p_total DECFLOAT

)

W wierszu {3} wywołuję polecenie PREPARE, które przygotowuje zapytanie SQL-owe z wiersza {2}. Następnie, za po-mocą pętli FOR, iteruję po wszystkich re-kordach i sumuję wartości produktów, wywołując EXECUTE w wierszu {4}. Zwróć

uwagę, że procedura calc _ value przyj-muje dwa parametry wejściowe, jeden wyjściowy i jeden wejściowo-wyjściowy, dlatego w klauzuli USING w wierszu {6} po-daję parametry wejściowe, w klauzuli INTO w wierszu {5} podaję parametry wyjścio-we, a parametr wejściowo-wyjściowy wy-stępuje w obu klauzulach. Przepisywa-nie wartości do/z parametrów wywoływa-nej procedury odbywa się w kolejności ich występowania w klauzulach USING i INTO. W Ramce 1 poniżej przedstawiłem sposób przekazywania wartości z/do procedury z Listingu 29.

Sprawdźmy, jak działa ta procedura. Zapisz kod z Listingu 29 do pliku p28.sql i skompi-luj, wołając:

db2 -td@ -vf p28.sql

Ustaw dane początkowe, wywołując:

db2 call clean()

Następnie uruchom procedurę poleceniem:

db2 call calculate_totals(?)

Procedura powinna zwrócić sumaryczną wartość wszystkich produktów na magazy-nie, tak jak pokazałem poniżej:

Wartości parametrów wyjściowych

--------------------------

Nazwa parametru: P_OUT

Wartość parametru: 2215.50

Status powrotu= 0

PodsumowanieW artykule przybliżyłem podstawowe kon-strukcje języka procedur składowanych w DB2, takie jak zmienne, instrukcje bloko-we, pętle, instrukcje obsługi błędów, kur-sory oraz metody dynamicznego wykony-wania zapytań. Mam nadzieję, że przed-stawione przykłady będą bardzo dobrym wstępem do programowania w języku SQL PL w DB2.

DARIUSZ DEPTADariusz Depta pracuje jako kierownik działu roz-woju technologicznego w firmie dostarczającej oprogramowanie dla sektora ubezpieczeń bez-pośrednich. Jego pasją jest tworzenie oprogra-mowania w różnych językach (Java, C/C++, Ob-ject Pascal, Erlang, Prolog, Ruby), a wolny czas spędza z rodziną na wyprawach pieszych i ro-werowych.Kontakt z autorem: [email protected]

Page 34: Sdj Extra 35 Db2

34

Tworzenie aplikacji

SDJ Extra 35

Terminologia w DB2Pierwszą barierą podczas poznawania nowe-go systemu zarządzania baz danych jest ko-nieczność zrozumienia pojęć, jakimi operu-ją jego użytkownicy. Patrząc na serwer IBM DB2 z punktu widzenia programisty lub ad-ministratora Oracle DB, zauważymy, iż wie-le pojęć jest ze sobą tożsamych. Inne wymaga-ją jedynie poznania nowej nazwy. Istnieją jed-nak i takie, które w poznawanym przez nas systemie mają zupełnie inne znaczenie niż to, do którego jesteśmy przyzwyczajeni. Przy-kładem takiego pojęcia są pakiety. W Orac-le pakiet jest obiektem, który grupuje pro-gramy PL/SQL oraz inne elementy, takie jak typy, zmienne, wyjątki czy kursory. Pakiet w DB2 jest obiektem zawierającym skompilo-waną wersję zapytań SQL oraz przygotowa-ne przez optymalizator plany zapytań. Bez-pośrednim odpowiednikiem pakietów Orac-le są w DB2 moduły.

W Tabeli 1 przedstawione zostało po-równanie pojęć w terminologii serwerów IBM DB2 i Oracle DB, uzupełnione ko-mentarzem. Zestawienie uzupełniono również o polskie nazewnictwo dla serwe-ra IBM DB2.

DB2 jak Oracle DBPo określeniu różnic w terminologii nowe-go systemu zarządzania bazami danych pro-gramista stawał przed kolejnym wyzwa-niem – poznaniem składni nowego dialek-tu SQL oraz języka procedur składowanych.

Wprowadzając na rynek wersję DB2 9.5, firma IBM udostępniła programistom apli-kacji na bazie Oracle DB szereg poziomów kompatybilności. Co więcej, ich zakres zo-stał znacząco rozszerzony w wersji 9.7 po-przez dodanie obsługi składni języka PL/SQL. Umożliwiono w ten sposób progra-mistom efektywniejsze przygotowanie ko-du dla bazy DB2 bez rezygnowania z istnie-jących przyzwyczajeń.

Sterowanie całym mechanizmem ograni-cza się do ustawienia wartości zmiennej sys-temowej DB2_COMPATIBILITY_VECTOR. Po-szczególne funkcjonalności dostępne w ba-zie Oracle przypisane są odpowiednim war-tościom w systemie heksadecymalnym. Ich suma pozwala na uruchomienie zbioru od-powiednich mechanizmów w bazie DB2 (patrz Tabela 2). Warto zauważyć, iż część funkcjonalności będzie uruchomiona je-

DB2 COBRA – Converting Oracle Becomes Really Affordable

Migracja aplikacji z bazy Oracle do DB2 wymusza na programistach rozpoznanie specyfiki nowego środowiska baz danych. Programiści muszą zapoznać się z różnicami w dialektach SQL, typach danych oraz dostępnych funkcjach. Muszą także pokonać swoje przyzwyczajenia, by skorzystać z nowych narzędzi. IBM wyszedł naprzeciw oczekiwaniom firm tworzących oprogramowanie współpracujące z bazami Oracle i wprowadził szereg mechanizmów kompatybilności w wersji DB2 9.5. Zakres usprawnień został znacznie rozszerzony w wersji 9.7.

Marcin Molak

Rysunek 1. Silnik kodu proceduralnego DB2 9.7

Page 35: Sdj Extra 35 Db2

DB2 COBRA – Converting Oracle Becomes Really Affordable

35www.sdjournal.org

Tabela 1. Pojęcia w systemach Oracle DB i IBM DB2

Oracle DB IBM DB2 Komentarz

Active log Active log (dziennik aktywny) Plik dziennika transakcji, który zawiera aktywne transakcje. Ten sam plik wykorzystywany jest do operacji wycofywania transakcji (rollback), jak i do naprawy bazy danych po awarii (crash recovery).

Alert log Administration notification log (dziennik powiadomień admini-stracyjnych)

Jest to podstawowe źródło informacji o zdarzeniach związanych z systemem DB2. Serwer za-pisuje w nim informacje takie jak status wykonania narzędzi (reorganizacja, kopia zapasowa), błędy aplikacji klienckich, błędy składowania danych czy zmiany w licencjonowaniu produk-tu. W systemach typu UNIX lub Linux dziennik ten jest dostępny w postaci pliku nazwa_instan-cji.nfy, a w systemach Windows – w dzienniku zdarzeń systemowych.

Archive log Archive log (Dzienniki archi-walne)

bdump directory Diagnostic log – db2diag.log (dziennik diagnostyczny)

Dziennik diagnostyczny jest przeznaczony głównie dla pomocy technicznej DB2. Zawiera wszystkie informacje diagnostyczne opisujące błędy (w tym zawarte w dzienniku powiado-mień administracyjnych).

Data block Data page (strona) Podstawowa jednostka operacji we/wy. W DB2 wspierane są następujące rozmiary stron: 4 KB, 8 KB, 16 KB, 32 KB.

Data buffer cache Buffer pool (pula buforów) DB2 pozwala na utworzenie dowolnej liczby puli buforów.

Data dictionary System catalog (słownik syste-mowy)

Słownik systemowy jest zbiorem meta danych przechowywanych w formie tabel i widoków. Dla każdej bazy serwer przygotowuje dwa zestawy widoków:SYSCAT – widoki tylko do odczytuSYSSTAT – widoki dla statystyk, z możliwością modyfikowania

Data file Container (kontener) Dane w DB2 są składowane w kontenerach. Kontenerem może być plik, katalog bądź urządze-nie surowe (raw device).

Database link Nickname (pseudonim) Pseudonim jest wskaźnikiem dla obiektu sfederowanej bazy danych.

Dual table Dual table / SYSDUMMY1 table (tabele Dual i SYSDUMMY1)

Podstawową tabelą dla ewaluacji wyrażeń w DB2 jest SYSIBM.SYSDUMMY1.

Dynamic performan-ce views

Snapshot monitor SQL admini-strative views (widoki admini-stracyjne dla monitora obrazów stanu)

Widoki administracyjne monitora obrazów stanu dostępne w schemacie SYSIBMADM, prze-chowują informacje o komponentach bazy danych.

Extent Extent (ekstent) Ekstent jest zbiorem kolejnych stron na dysku przydzielonych dla określonego obiektu.

init.ora i Server Para-meter File (SPFILE)

Database manager configuration file (plik konfiguracyjny instancji) i database configuration file (plik konfiguracyjny bazy danych)

Pojedyncza instancja DB2 może obsługiwać wiele baz danych. Dostęp do plików konfiguracyj-nych jest zapewniony poprzez komendy administracyjne GET i UPDATE DBM CONFIG oraz GET i UPDATE DB CONFIG.

Instance Instance (instancja) / database manager (menadżer baz danych)

Instancja jest niezależnym środowiskiem do pracy z bazami DB2. W DB2 instancja jest również określana mianem menadżera bazy danych. Z instancją typu serwer związany jest zbiór proce-sów obsługujących bazy danych oraz wspólny obszar pamięci.

Large pool Utility heap (sterta narzędzi) Fragment pamięci używany przez narzędzia backup, restore i load.

Library cache Package cache (pamięć podręcz-na pakietów)

Pamięć przechowująca skompilowane plany realizacji zapytań SQL i XQuery.

Materialized view Materialized query table – MQT (Zmaterializowany widok)

MQT to tabela, której definicję oparto o wynik zapytania. Decyzja o użyciu MQT bądź tabeli ba-zowej jest podejmowana przez optymalizator DB2.

ORACLE_SID DB2INSTANCE

Package Module (moduł) Obiekty wprowadzone w wersji DB2 9.7. Równolegle wyodrębniono struktury pakietów PL/SQL.

Procedural Language/Structured Query Language (PL/SQL)

SQL Procedural Language (SQL PL)

SQL PL (podzbiór SQL/PSM) jest językiem pozwalającym na definiowanie logiki biznesowej w bazie danych. Od wersji DB2 9.7 możliwe jest również wykorzystywanie składni PL/SQL.

Program global area (PGA)

Application shared memory (pa-mięć współużytkowana aplikacji) i agent private memory (prywat-na pamięć agenta)

Pamięć współużytkowana aplikacji to obszar pamięci pozwalający na współdzielenie danych wynikowych między bazą danych a aplikacją.Prywatna pamięć agenta jest używana do obsługi danej aplikacji (sortowanie, utrzymanie kon-tekstu danej sesji, informacje o kursorach).

Redo log Transaction log (dziennik transakcji) Przechowuje informacje o transakcjach. Używany również w procesie odtwarzania bazy danych.

Role Role (rola) Obiekt dostępny od wersji DB2 9.5.

Segment Storage object (obiekt przecho-wywania)

Synonym Alias Alias stanowi jedynie alternatywną nazwę dla tabeli, widoku, pseudonimu lub innego aliasu. Nie jest mechanizmem kontroli wersji procedury składowanej bądź funkcji użytkownika (do te-go celu służy instrukcja SET PATH).

System global area (SGA)

Instance shared memory (współ-użytkowana pamięć instan-cji) i database shared memory (współużytkowana pamięć ba-zy danych)

Współużytkowane pamięci na poziomie instancji i bazy danych mogą być mapowane na SGA. Pierwsza z nich przechowuje informacje niezbędne do działania instancji, takie jak informacje o połączeniach czy uprawnieniach użytkowników. Druga obejmuje struktury pamięciowe ba-zy danych, takie jak pule buforów, bufor dziennika transakcji czy pamięć podręczna pakietów.

User global area (UGA)

Application global memory (cał-kowita pamięć aplikacji)

Całkowita pamięć dla obsługi aplikacji.

Page 36: Sdj Extra 35 Db2

36

Tworzenie aplikacji

SDJ Extra 35

dynie dla nowoutworzonych baz danych, a wszystkie zmiany ustawienia wymagają re-startu motoru.

Ustawienia wartości zmiennej dokonuje-my poleceniem db2set, np.:

db2stop

db2set DB2_COMPATIBILITY_VECTOR = ORA

db2start

Powyższa zmienna uruchamia maksymalny poziom kompatybilności z bazą Oracle DB. Wartość ORA została wprowadzona w wer-sji DB2 9.7 i jest zalecana dla aplikacji prze-noszonych z baz Oracle DB.

Typy danych i kolekcjeMigrując istniejące aplikacje na serwer IBM DB2, programiści często spotyka-li się z koniecznością mapowania typów z baz danych Oracle DB. Proces ten mógł być zautomatyzowany np. poprzez zasto-sowanie aplikacji IBM Migration Toolkit, jednak nadal zdarzały się sytuacje, w któ-rych decyzję musiał podjąć człowiek, np. z powodu braku odpowiadającego typu bądź różnic w implementacji (np. dla typu Da-te). W ramach wersji 9.5 wprowadzono dwa nowe typy – typ zmiennoprzecin-kowy DECFLOAT, wspomagany sprzętowo przez procesory rodziny IBM Power 6, oraz typ tablicowy (ARRAY). W wersji 9.7 wpro-wadzono wiele typów znanych z serwera danych Oracle DB.

W ramach poziomów kompatybilno-ści zostały wprowadzone typy: zmienno-przecinkowy NUMBER, łańcuch znaków VARCHAR2 oraz implementacja formatu cza-sowego DATE, zgodnego ze strukturą Orac-le. Wykorzystanie typu NUMBER opiera się na jego bezpośrednim mapowaniu na typ DECFLOAT(16) bądź na typ DECIMAL przy określonej precyzji. Typ DATE został opar-ty na typie TIMESTAMP (o precyzji rów-nej 0), a typ VARCHAR2 na typie VARCHAR z zachowaniem logiki działania charaktery-stycznej dla typu Oracle DB, to jest ma-powaniu pustego ciągu znaków na wartość NULL. Zobrazowanie mechanizmu mapo-wania pokazano na Listingu 1. Na bazie za-pytania SQL tworzona jest tabela ZADANIA zawierająca typy NUMBER, VARCHAR2 i DATE. Następnie struktura tabeli wewnętrznie zarządzana przez DB2 jest wyświetlana poleceniem DESCRIBE TABLE.

W kodzie proceduralnym możliwe jest również wykorzystanie typu logicznego BOOLEAN, rekordu ROW (RECORD), typów za-kotwiczonych ANCHOR(%TYPE) i ANCHOR ROW(%ROWTYPE), kursorów (REF CURSOR) oraz tablic asocjacyjnych (INDEX BY).

W tym miejscu warto również zaznaczyć, iż nie jest już wymagane jawne rzutowanie pomiędzy typami numerycznymi i łańcu-

Tabela 2. Poziomy kompatybilności w DB2

Wartość Nazwa funkcjonalności DB2 9.5 DB2 9.7 Wymagane przed utwo-rzeniem bazy

1(0x01) ROWNUM Tak Tak Nie

2(0x02) DUAL Tak Nie dotyczy Tak

3(0x04) Operator (+) dla OUTER JOIN Tak Tak Nie

4(0x08) Wyrażenia hierarchiczne Tak Tak Nie

5(0x10) NUMBER Nie Tak Tak

6(0x20) Oracle DATE Nie Tak Tak

7(0x40) VARCHAR2 Nie Tak Tak

8(0x80) TRUNCATE TABLE Nie Tak Nie

9(0x100) Obsługa łańcuchów znaków Nie Tak Nie

10(0x200) Obsługa kolekcji danych Nie Tak Nie

11(0x400) Data dictionary – zestaw wido-ków administracyjnych

Nie Tak Tak

12(0x800) Kompilacja PL/SQL Nie Tak Nie

Listing 1. Mapowanie typów przy włączonym poziomie kompatybilności ORA

CREATE TABLE cobra.zadania (

id NUMBER(5) NOT NULL PRIMARY KEY,

zadanie VARCHAR2(300),

data DATE

);

DB20000I Wykonanie komendy SQL zakończyło się pomyślnie.

DESCRIBE TABLE cobra.zadania;

Schemat Długość

Nazwa kolumny typu danych Nazwa typu danych kolumny Zera skali

------------ --------- ----------------- -------- ----- ------

ID SYSIBM DECIMAL 5 0 Nie

ZADANIE SYSIBM VARCHAR 300 0 Tak

DATA SYSIBM TIMESTAMP 7 0 Tak

Wybrano 3 rekordy.

Tabela 3. Moduły procedur zaimplementowane w DB2 9.7

Nazwa Opis

DBMS_ALERT Zawiera procedury do rejestrowania, wysyłania i odbierania alertów.

DBMS_JOB Obejmuje procedury do zarządzania zadaniami.

DBMS_LOB Dostarcza procedur operujących na obiektach LOB.

DBMS_OUTPUT Stanowi zbiór procedur wkładających i wyjmujących komunikaty z bufora wia-domości. Jest podstawą do debugowania aplikacji.

DBMS_PIPE Zapewnia komunikację między sesjami poprzez procedury obsługi strumieni.

DBMS_SQL Dostarcza procedury składowane do wywoływania dynamicznego kodu SQL, w tym operacji DML i DDL.

DBMS_UTILITY Obejmuje zbiór procedur narzędziowych.

UTL_DIR Zawiera procedury do zarządzania aliasami dla katalogów systemu plików (na rzecz procedur modułu UTL_FILE).

UTL_FILE Stanowi zbiór procedur pozwalający na odczyt i zapis plików z systemu plików serwera bazodanowego.

UTL_MAIL Moduł pozwalający na zarządzanie pocztą elektroniczną (wysyłanie wiadomo-ści).

UTL_SMTP Dostarcza procedur do wysyłania wiadomości elektronicznych poprzez proto-kół SMTP.

Page 37: Sdj Extra 35 Db2

DB2 COBRA – Converting Oracle Becomes Really Affordable

37www.sdjournal.org

chami znaków oraz łańcuchami znaków a typami date/time/timestamp.

Funkcje i składnia SQLPo przygotowaniu struktur danych nadcho-dzi czas na uruchomienie własnych skryp-tów. Jeśli użyliśmy składni oraz funkcji wy-kraczających poza zakres ANSI SQL, poja-wia się wówczas pytanie, czy przygotowa-ny kod zadziała? W wersji DB2 9.5 również ten aspekt motoru bazy danych uległ mody-fikacjom.

Na początek przyjrzyjmy się składni dla wyrażeń hierarchicznych. W DB2 w ślad za standardem ANSI używana była skład-nia WITH … UNION ALL. Tymczasem w Orac-le DB wprowadzono do dialektu SQL alter-natywę w postaci klauzuli CONNECT BY. Mi-mo iż pod kątem możliwości generowania złożonych zbiorów wynikowych standardo-wa rekursja jest co najmniej tak samo sku-

teczna i wydajna, to jednak CONNECT BY sta-ła się w oczach programistów bardziej przy-jazna. Dlatego też od wersji DB2 9.5 moż-liwe jest jej uruchomienie w ramach pozio-mów kompatybilności (wartość 0x08 dla wektora). Jej użycie przedstawiono na Li-stingu 2.

Podobnie jak CONNECT BY, w DB2 9.5 wprowadzono również możliwość stoso-wania operatora (+) dla złączeń zewnętrz-nych (ang. outer join), klauzuli UNIQUE jako synonimu DISTINCT czy klauzuli MINUS ja-ko alternatywy dla EXCEPT. Na tym tle war-to również wspomnieć o możliwości wyko-rzystania tabeli DUAL do ewaluacji wyrażeń oraz pseudokolumny ROWNUM użytecznej w stronicowaniu danych (realizowanym w sys-temach Open Source przez klauzule LIMIT i OFFSET).

Niezależnie od rozszerzenia składni SQL, w wersji DB2 9.5 poszerzono wachlarz do-

stępnych funkcji. Wśród nich znalazły się funkcje operujące na bitach (BITAND, BITANDNOT, BITOR, BITXOR oraz BITNOT), wy-szukujące najmniejszą i największą wartość w zbiorze argumentów (LEAST i GREATEST). Wśród nowych funkcji znalazły się również bardzo popularne w Oracle DB funkcje NVL i DECODE. Pierwsza z nich pozwala na wy-szukanie pierwszej wartości oznaczonej w zbiorze argumentów, dzięki temu doskona-le nadaje się do jawnego określenia wartości dla pól z wartością NULL w wyniku zapyta-nia. Druga stanowi natomiast implementa-cję funkcji warunkowej, pozwalając na łatwe rzutowanie wartości jednego zbioru w dru-gi. Na Listingu 3 przedstawiono przykłado-we wykorzystanie funkcji DECODE.

Kod PL/SQLOstatnim i zarazem bardzo istotnym ele-mentem pracy programisty baz danych w

Listing 2. Użycie klauzuli CONNECT BY w DB2

Plik: struktura.sql

-- Tworzenie tabeli

CREATE TABLE cobra.struktura(

id BIGINT NOT NULL PRIMARY KEY,

stanowisko VARCHAR(30),

przel_id BIGINT REFERENCES cobra.struktura(id)

);

-- Uzupełnienie danych

INSERT INTO cobra.struktura VALUES

(1, 'specjalista', 3),

(2, 'konsultant', 3),

(3, 'kierownik', 4),

(4, 'menadżer', 5),

(5, 'dyrektor', NULL);

Plik: drzewo.sql

SELECT LEVEL as poziom,

stanowisko,

CONNECT_BY_ROOT stanowisko AS najwyzsze,

SYS_CONNECT_BY_PATH(stanowisko, ':') AS struktura

FROM cobra.struktura

START WITH stanowisko = 'menadżer'

CONNECT BY PRIOR id = przel_id

ORDER SIBLINGS BY stanowisko;

POZIOM STANOWISKO NAJWYZSZE STRUKTURA

----------- ------------------------------ ------------------------------ -------------------------------

1 menadżer menadżer :menadżer

2 kierownik menadżer :menadżer:kierownik

3 konsultant menadżer :menadżer:kierownik:konsultant

3 specjalista menadżer :menadżer:kierownik:specjalista

Wybrano 4 rekordy.

Page 38: Sdj Extra 35 Db2

38

Tworzenie aplikacji

SDJ Extra 35

procesie migrowania aplikacji jest przenie-sienie istniejącej logiki biznesowej do no-wego systemu. W tej materii serwery baz danych DB2 do wersji 9.5 włącznie wy-muszały przeniesienie istniejącego kodu do języka SQL PL. Wraz z wersją 9.7 pro-gramista otrzymuje do dyspozycji wbudo-wany kompilator kodu PL/SQL (patrz Ry-sunek 1).

Aby uruchomić kod PL/SQL z pozio-mu Procesora wiersza poleceń (CLP), musi-my ustawić środowisko komendą SET SQL-COMPAT PLSQL. Od tego momentu zna-kiem terminującym dla wywoływanych skryptów będzie znak slash (/). Pozwoli to nam na przygotowanie elementów logiki biz-nesowej zgodnie z nową składnią.

Pierwszą i zarazem najprostszą obsługiwa-ną strukturą są bloki. Przykładowy blok zo-stał przedstawiony na Listingu 4. Blok ten składa się z sekcji deklaracji oraz z sekcji wy-konawczej. W pierwszej z nich deklarujemy zmienną typu VARCHAR2, w drugiej do-konujemy przypisania wartości oraz wyświe-tlenia tekstu.

Przykład ten obrazuje jednak nie tylko możliwość użycia elementów składni PL/SQL, takich jak bloki czy operator przypisa-nia ‘:=’. Wyświetlenie tekstu na terminalu wiąże się bowiem z użyciem procedury nale-żącej do pakietu (modułu w SQL PL) DBMS_OUTPUT. Wraz z kompilatorem PL/SQL zosta-ły bowiem dostarczone zbiory procedur wy-korzystywanych przez programistów Oracle DB (patrz Tabela 3).

W ramach DB2 9.7 możliwe jest rów-nież tworzenie własnych pakietów proce-dur i funkcji PL/SQL. Celem stworzenia pakietu PL/SQL należy przygotować spe-cyfikację pakietu, zawierającą deklaracje je-go elementów. Drugim elementem pakie-tu jest jego ciało, zawierające implementa-cje zadeklarowanych wcześniej elementów. Listing 5 prezentuje specyfikacje i ciało pa-kietu str_admin, powiązanego ze strukturą stanowisk (patrz Listing 2).

Na bazie tego przykładu warto podkreślić dodanie obsługi klauzuli OR REPLACE dla obiektów, takich jak aliasy, funkcje, moduły (i pakiety PL/SQL), pseudonimy, procedury, sekwencje, wyzwalacze (ang triggers), widoki oraz zmienne.

CLPPLUS i słownik bazy danychAnalizując ścieżkę migracji aplikacji, posłu-giwaliśmy się skryptami, które były urucha-miane ze środowiska Procesora wiersza ko-mend (CLP). Przyglądając się pracy progra-mistów i administratorów Oracle DB, moż-na zauważyć, iż ich praca bardzo często opiera się na wywołaniu przygotowanych wcześniej skryptów w terminalu SQL*Plus. Co więcej, narzędzie to umożliwia bardzo wygodne formatowanie zbiorów wyniko-

Listing 3. Przykładowe wykorzystanie funkcji DECODE

SELECT

imie, nazwisko,

DECODE(plec,'M','Mężczyzna','K','Kobieta') as plec

FROM cobra.osoby

Listing 4. Przykładowy blok PL/SQL

SET SQLCOMPAT PLSQL;

SET SERVEROUTPUT ON;

DECLARE

vText VARCHAR2(15);

BEGIN

vText := 'Witaj świecie!';

DBMS_OUTPUT.PUT_LINE(vText);

END;

/

SET SQLCOMPAT DB2;

Rysunek 2. Narzędzie CLPPLUS

Ramka 1. Lista dostępnych widoków administracyjnych

Ogólne

DICTIONARYDICT_COLUMNS*_CATALOG*_DEPENDENCIES*_OBJECTS*_SEQUENCESUSER_TABLESPACESDBA_TABLESPACES

Programowanie i PL/SQL*_PROCEDURES*_SOURCE*_TRIGGERS*_ERRORS*_ARGUMENTS

Bezpieczeństwo

USER_ROLE_PRIVSDBA_ROLE_PRIVSROLE_ROLE_PRIVSSESSION_ROLESUSER_SYS_PRIVSDBA_SYS_PRIVSROLE_SYS_PRIVSSESSION_PRIVS*_TAB_PRIVSROLE_TAB_PRIVSUSER_TAB_PRIVS_MADEALL_TAB_PRIVS_MADEUSER_TAB_PRIVS_RECDALL_TAB_PRIVS_RECDDBA_ROLESDBA_USERS

Tabele i widoki

*_CONSTRAINTS*_CONS_COLUMNS*_INDEXES*_IND_COLUMNS*_TAB_PARTITIONS*_PART_TABLES*_PART_KEY_COLUMNS*_SYNONYMS*_TABLES*_TAB_COMMENTS*_TAB_COLUMNS*_COL_COMMENTS*_TAB_COL_STATISTICS*_VIEWS*_VIEW_COLUMNS

* = USER_DBA_ALL

Page 39: Sdj Extra 35 Db2

DB2 COBRA – Converting Oracle Becomes Really Affordable

39www.sdjournal.org

wych, pozwalając na przygotowanie pro-stych raportów. Dlatego też w DB2 9.7 wprowadzono nowe narzędzie tekstowe CLPPLUS. Zachowuje ono cechy swojego pierwowzoru, przyspieszając poznanie ser-wera firmy IBM.

Aby połączyć się do wybranej bazy danych, wykonujemy komendę:

connect nazwa_użytkownika@serwer:port/

baza_danych

np.

connect db2admin@localhost:50000/sample

Składnia ta może zostać łatwo rozszerzona o hasło użytkownika do postaci:

connect nazwa_użytkownika/hasło@serwer:

port/baza_danych

np.

connect db2admin/tajne@localhost:50000/

sample

Równie łatwo można poznać wszystkie ko-mendy procesora CLPPLUS. Wystarczy wpi-sać polecenie:

HELP INDEX

Ukłonem w stronę administratorów jest również wprowadzenie struktur słowni-ka bazy danych znanych im z Oracle DB (patrz Ramka). Dzięki temu dotychczas wykonywane czynności mogą być w łatwy sposób przeniesione na nowy serwer baz danych.

PodsumowanieIdeą niniejszego artykułu było pokazanie no-wych funkcjonalności dostępnych w serwe-rach danych DB2 w wersjach 9.5 i 9.7, uła-twiających proces migrowania i wdrażania aplikacji na nowym serwerze danych. Stano-wi on jednak dopiero wstęp do poznania peł-ni możliwości baz danych DB2 – do czego serdecznie zachęcam.

Listing 5. Specyfikacja i ciało pakietu str_admin

SET SQLCOMPAT PLSQL;

CREATE OR REPLACE PACKAGE cobra.str_admin

IS

FUNCTION wyswietl_przel (

p_id BIGINT

)

RETURN BIGINT;

PROCEDURE dodaj_stanowisko (

p_stanowisko VARCHAR(30),

p_przel_id BIGINT

);

END cobra.str_admin;

/

CREATE OR REPLACE PACKAGE BODY cobra.str_admin

IS

FUNCTION wyswietl_przel (

p_id BIGINT

)

RETURN BIGINT

IS

v_szef_id BIGINT;

BEGIN

SELECT przel_id INTO v_szef_id FROM cobra.struktura WHERE id=p_id;

RETURN v_szef_id;

EXCEPTION

WHEN NO_DATA_FOUND THEN

DBMS_OUTPUT.PUT_LINE('Brak stanowiska o numerze ' || p_id);

RETURN -1;

END;

PROCEDURE dodaj_stanowisko (

p_stanowisko VARCHAR(30),

p_przel_id BIGINT

)

IS

v_id BIGINT:=0;

BEGIN

SELECT MAX(id) INTO v_id FROM cobra.struktura;

v_id := v_id + 1;

INSERT INTO cobra.struktura VALUES (v_id, p_stanowisko, p_przel_id);

RETURN 0;

EXCEPTION

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE('The following is SQLERRM:');

DBMS_OUTPUT.PUT_LINE(SQLERRM);

DBMS_OUTPUT.PUT_LINE('The following is SQLCODE:');

DBMS_OUTPUT.PUT_LINE(SQLCODE);

RETURN -1;

END;

END;

/

SET SQLCOMPAT DB2;

MARCin MOLAKSpecjalista w zakresie zarządzania danymi w dziale oprogramowania IBM Polska i ambasa-dor programu inicjatywy akademickiej IBM. Od-powiada za wsparcie serwerów danych DB2 i so-lidDB oraz narzędzi z rodzin Optim i Data Stu-dio. Absolwent Wydziału Fizyki Politechniki War-szawskiej. Prywatnie pasjonat nowych technolo-gii oraz miłośnik fantastyki. Kontakt z autorem: [email protected]

Page 40: Sdj Extra 35 Db2

40

Tworzenie aplikacji

SDJ Extra 35

Wówczas w laboratoriach firmy IBM trzej inżynierowie – Char-les Goldfarb, Edward Mosher i

Raymond Lorie opracowali uogólniony język znaczników (GML). Znaczniki te pozwalały twórcom dokumentu na wydzielenie w pro-sty sposób jego logicznych części. Stanowiło to znaczące usprawnienie wobec wcześniej-szego obowiązku fizycznego formatowania elementów, charakterystycznego dla ówcze-snych typów drukarek.

W 1986 roku, bazując na doświadcze-niach języka GML, opracowano korporacyj-ny standard opisu dokumentów – SGML (ang. Standard Generalized Markup Langu-age). Jego specyfikacja zakładała jednak nie tylko uproszczenie definiowania struktu-ry w ramach znaczników, ale również wpro-wadzała możliwość określania zasad składni unifikujących sposoby konstrukcji oraz wy-miany dokumentów. W praktyce wymaga-ło to jednak zdefiniowania obok dokumen-tu również reguł jego zapisu oraz definicji je-go typu (DTD).

I właśnie trudności w implementacji języ-ka SGML zaowocowały przygotowaniem no-wej specyfikacji, w której jawnie zdefiniowa-no reguły zapisu dokumentów, pozostawiając twórcom definicję ich nowego typu w posta-ci określonych znaczników. W ten sposób w 1997 roku powstaje język XML.

Zastosowanie XMLOtwierając dokument XML w edytorze tek-stowym, poznajemy jego podstawową zaletę – czytelność struktury danych. Struktury re-prezentowanej w postaci drzewa elementów oraz określających je atrybutów. Dzięki temu aplikacje mogły odwzorować swoje obiekty do samoopisującego się formatu tekstowego, zrozumiałego dla innych systemów. Warto jednak zwrócić uwagę na fakt, iż zastosowa-nie XML nie ogranicza się do samej wymia-ny informacji. O jego uniwersalności świad-czy cała gama standardów i typów dokumen-tów, z którymi mamy częsty kontakt w prze-tworzonej postaci.

Najpopularniejszymi czytnikami doku-mentów XML są przeglądarki internetowe. To za ich pośrednictwem oglądamy codzien-nie strony internetowe, napisane w języku XHTML. Coraz częściej wypełniamy także dane w formularzach, dokonujących auto-matycznej walidacji i przygotowanych w jed-nej z implementacji XForms (np. IBM Lotus Forms). Kanały informacyjne RSS i ATOM pozwalają nam natomiast aktualizować na bieżąco naszą wiedzę.

Popularnym zastosowaniem XML sta-ły się również standardy Open Document oraz Office Open XML zaimplementowane w pakietach biurowych celem zapisywania plików zawierających arkusze kalkulacyjne,

prezentacje czy publikacje tekstowe. Obok nich powstały także branżowe formaty, wśród których można wymienić standardy organizacji turystycznych (OTA), ubezpie-czeniowych (ACORD), finansowych rynku instrumentów pochodnych (FpML) czy bi-bliotek (MARC XML) i archiwów (EAD).

W ten sposób, na tle wymienionych przy-kładów stanowiących ułamek zastosowań XML, pojawia się potrzeba tworzenia repo-zytoriów dokumentów XML dających możli-wość wydajnego zarządzania informacją.

XML a bazy danychPodczas planowania repozytorium informa-cji często nasz wybór pada na systemy za-rządzania bazami danych. Oferują one wyso-ką wydajność dla przygotowywanych rozwią-zań, gwarantując jednocześnie ich pełną nie-zawodność i rozbudowaną funkcjonalność.

Należy jednak zwrócić uwagę na fakt, iż powyższe deklaracje często ograniczają się do danych relacyjnych. Dla struktur hierarchicz-nych, jakimi są dokumenty XML, obsługa re-alizowana jest jedynie na zasadzie rozszerze-nia funkcjonalnego. Najczęściej użytkownik jest postawiony przed koniecznością wybra-nia jednej z dwóch metod zarządzania do-kumentami.

Podstawową metodą składowania doku-mentów XML w bazie danych jest ich zapisy-

<XML> w praktyceRealizacja kolejnych wymagań biznesowych współczesnego rynku stawia przed działami IT szereg nowych wyzwań. Podstawowym wymogiem dla tworzonych przez nie rozwiązań staje się zapewnienie integracji z używanymi dotychczas aplikacjami. Proces ten, ze względu na zróżnicowanie obsługiwanych struktur, musi zapewniać możliwość zarówno transformacji, jak i pełnej walidacji przekazywanych danych. Jednocześnie stawiane są wysokie wymogi bezpieczeństwa. Przez to implementacja nowych rozwiązań mogłaby być zarazem procesem skomplikowanym i czasochłonnym. W praktyce jednak z pomocą przychodzą standardy oparte na języku XML. Języku, którego niesłabnąca popularność na rynku trwa już 12 lat od opublikowania pierwszej specyfikacji. Tym bardziej ciężko jest uwierzyć, iż początki jego historii sięgają lat sześćdziesiątych dwudziestego wieku.

Marcin Molak

Page 41: Sdj Extra 35 Db2

<XML> w praktyce

41www.sdjournal.org

wanie w pełnej, niezmienionej, tekstowej po-staci w ramach jednej kolumny tabel, której typ dziedziczy po typie tekstowym (na ogół CLOB). Dzięki temu zachowana jest pełna elastyczność w definiowaniu, modyfikowa-niu i transformowaniu struktury danych. Z drugiej jednak strony każde odwołanie do elementów dokumentu wymaga od systemu powtórzenia procesu analizy składniowej, co jest procesem nieefektywnym. Dlatego, w ce-lu poprawienia wydajności, stosuje się struk-tury pomocnicze, których zadaniem jest zwiększenie efektywności dostępu do naj-częściej wykorzystywanych elementów drze-wa dokumentów. W dłuższym odcinku czasu mogą one jednak powodować dodatkowy na-rzut, związany z kosztami ich utrzymywania.

Druga metoda opiera się o dekompozy-cję dokumentów XML do struktury tabel połączonych ze sobą relacjami. Niewątpli-wą jej zaletą jest zapewnienie wysokiej wy-dajności dostępu do elementów dokumentu XML. Jednak struktura obiektów powstaje na bazie przygotowanego wcześniej schema-tu walidacji dokumentu, co powoduje nie tylko jej nadmiarowość wobec zapisywanych danych (np. braku elementów opcjonalnych w dokumencie XML), ale przede wszystkim brak elastyczności. Każdorazowa zmiana schematu pociąga bowiem za sobą kosztow-ną operację modyfikacji i reorganizacji zało-żonych tabel.

Wymienione powyżej problemy powo-dują, iż często obszar zainteresowań doty-czących infrastruktury dla repozytorium XML rozszerza się na bazy dedykowane. Wadą tych ostatnich jest jednak brak narzę-dzi umożliwiających łatwą integrację po-między nowym medium danych a danymi relacyjnymi. Tym samym wzrastają koszty utrzymania spójności i bezpieczeństwa in-formacji.

Rozwiązaniem wysoce pożądanym stała się zatem integracja dwóch silników – re-lacyjnego i hierarchicznego w ramach jed-nego systemu zarządzania bazami danych. I właśnie taka integracja została zapropono-wana w 2006 roku przez IBM wraz z wpro-wadzeniem na rynek serwera danych DB2 w wersji 9.1.

Obsługa XML w DB2Wprowadzenie natywnego wsparcia dla do-kumentów XML wymagało od zespołu pro-gramistów DB2 szeregu modyfikacji silnika baz danych. Podstawowym warunkiem by-ło dodanie nowego typu danych – XML. Z punktu widzenia relacyjnych baz, kolumny tego typu powinny występować w tabelach zarówno samodzielnie, jak i obok innych ko-lumn typów standardowych. Z drugiej stro-ny hierarchiczne struktury dokumentów wy-magały innych zasad zapisu danych, związa-nych ze specyfikacją XML::DB. Ważnym pro-

blemem było również wprowadzenie jedno-czesnej obsługi języków zapytań, charaktery-stycznych dla danych relacyjnych (SQL) i do-kumentów XML (XQuery i XPath).

Powyższe zagadnienia, a także wiele innych związanych ze światami – relacyjnym i XML, stanowiły doskonały punkt wyjścia dla nowej technologii – pureXML. Jako że możliwości

Rysunek 1. Fizyczna postać drzewa dokumentu dla kolumny typu XML w wersji 9.5 i nowszych. Dla ID równego PR28 dokument jest umieszczony w repozytorium hierarchicznym (not-inline), dla pozostałych na poziomie rekordów relacyjnych (inline)

Dane relacyjneIndeksy

Repozytorium hierarchiczne

Indeksyfragmentów

ID ... DOC (XML)PR27 ... PR28 ... ACC ...

Listing 1. Przygotowanie tabel dla aplikacji

CREATE TABLE xmlapp.pracownicy(

id bigint generated always as identity,

imie varchar(20) not null,

nazwisko varchar(25) not null,

email varchar(35)

) IN relData;

ALTER TABLE xmlapp.pracownicy ADD CONSTRAINT prac_pk PRIMARY KEY (id);

CREATE TABLE xmlapp.zadania(

id bigint generated always as identity,

prac_id bigint,

zadanie xml INLINE LENGTH 500 not null

) COMPRESS YES IN relData LONG IN xmlData;

ALTER TABLE xmlapp.zadania ADD CONSTRAINT zad_pk PRIMARY KEY (id);

ALTER TABLE xmlapp.zadania ADD CONSTRAINT zad_fk FOREIGN KEY (prac_id) REFERENCES

xmlapp.pracownicy;

CREATE TABLE xmlapp.xslt(

id bigint generated always as identity,

nazwa char(5) not null,

dokument xml not null

) IN relData LONG IN xmlData;

ALTER TABLE xmlapp.XSLT ADD CONSTRAINT xslt_uq UNIQUE (nazwa);

Page 42: Sdj Extra 35 Db2

42

Tworzenie aplikacji

SDJ Extra 35

najlepiej poznać poprzez praktykę, przygotu-jemy elementy prostego szkieletu aplikacji o umownej nazwie terminarz.

Przygotowanie struktur danychNaszym pierwszym krokiem będzie przy-gotowanie struktur danych, w których bę-dziemy umieszczać dane aplikacji, oczy-

wiście zapisane w postaci danych relacyj-nych oraz dokumentów XML. Zaczniemy od stworzenia bazy danych o umownej na-zwie termdb. W tym celu wykonujemy po-lecenie:

create database termdb using codeset UTF-8

territory pl

Jak łatwo zauważyć, przygotowanie ba-zy danych obsługującej XML nie wyma-ga definiowania żadnych dodatkowych pa-rametrów związanych z dodatkowym ty-pem danych. W tym miejscu należy zwró-cić jedynie uwagę na fakt istniejącego ogra-niczenia w wersji 9.1 – bazy dla dokumen-tów XML musiały używać kodowania ty-

Listing 2. Dokumenty XML – zadania związane z przygotowaniem artykułu

Plik: zadanie-artykul.xml

<?xml version="1.0" encoding="UTF-8"?>

<p:zadanie data="2009-03-05" typ="artykul"

xmlns:p="http://mmolak.pl/Zadanie"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://mmolak.pl/Zadanie Zadanie.xsd">

<nazwa>XML w praktyce</nazwa>

<opis>Artykuł przedstawiający możliwości technologii pureXML w

bazie danych DB2 9.7. Autor przedstawi w nim prostą aplikację,

umożliwiającą zarządzanie zadaniami pracowników opisanymi w

dokumentach XML</opis>

<magazyn>SDJ Extra!</magazyn>

</p:zadanie>

Plik: zadanie-spotkanie1.xml

<?xml version="1.0" encoding="UTF-8"?>

<p:zadanie data="2009-03-01" typ="spotkanie"

xmlns:p="http://mmolak.pl/Zadanie"

xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance

xsi:schemaLocation="http://mmolak.pl/Zadanie Zadanie.xsd ">

<nazwa>Spotkanie z osobami technicznymi w kwestii SDJ Extra!</nazwa>

<opis>Opracowanie tematyki artykułów SDJ Extra!</opis>

<godzinaStart>12:00:00</godzinaStart>

<godzinaStop>13:00:00</godzinaStop>

<osoby>Marcin Molak, Artur Wroński</osoby>

</p:zadanie>

Plik: zadanie-spotkanie2.xml

<?xml version="1.0" encoding="UTF-8"?>

<p:zadanie data="2009-03-01" typ="spotkanie"

xmlns:p=http://mmolak.pl/Zadanie

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://mmolak.pl/Zadanie Zadanie.xsd ">

<nazwa>Spotkanie z marketingiem w kwestii SDJ Extra!</nazwa>

<opis>Omówienie koncepcji SDJ Extra!</opis>

<godzinaStart>13:00:00</godzinaStart>

<godzinaStop>14:00:00</godzinaStop>

</p:zadanie>

Listing 3. Procedura składowana do publikowania zadań

CREATE PROCEDURE XMLAPP.DODAJZADANIE ( IN PRACOWNIK BIGINT, IN ZADANIE XML, OUT ID_ZADANIA INTEGER )

P1: BEGIN

DECLARE ID_ZADANIA_TMP INTEGER DEFAULT 0;

DECLARE cursor1 CURSOR FOR

SELECT ID FROM NEW TABLE( INSERT INTO XMLAPP.ZADANIA(PRAC_ID,ZADANIE)

VALUES( DODAJZADANIE.PRACOWNIK, DODAJZADANIE.ZADANIE ));

OPEN cursor1;

FETCH cursor1 INTO ID_ZADANIA_TMP ;

SET ID_ZADANIA = ID_ZADANIA_TMP;

CLOSE cursor1;

END P1

@

Page 43: Sdj Extra 35 Db2

<XML> w praktyce

43www.sdjournal.org

pu Unicode. W wersji 9.5 ograniczenie to zostało jednak zniesione. Z drugiej stro-ny strona kodowa UTF-8 stała się warto-ścią domyślną.

Po przygotowaniu bazy danych mogliby-śmy od razu przystąpić do utworzenia tabel z kolumnami typu XML. Nim jednak to uczy-nimy, zapoznajmy się z mechanizmami zapi-su danych. Dla DB2 najmniejszą porcją aloka-cji jest tak zwana strona (ang. page). W przy-padku danych relacyjnych stanowi ona ob-

szar pamięci, w którym umieszczane są ko-lejne rekordy tabeli. Ponieważ w ramach jed-nej strony musi być zapisany minimum jeden rekord, jej wielkość determinuje maksymalny rozmiar rekordu. Zasada ta nie dotyczy jedy-nie kolumn dla dużych obiektów tekstowych (CLOB) i binarnych (BLOB), które są dzielo-ne na kolejne strony.

W przypadku repozytorium XML proces jest bardziej złożony. Dokument, dostarczo-ny w postaci tekstowej, jest analizowany skła-

dniowo i na jego podstawie zostaje przygo-towana hierarchiczna struktura. Następnie wszystkie nazwy węzłów są słownikowane i zastępowane wartościami numerycznymi. Ostatnim krokiem jest podział drzewa XML na fragmenty. Zostają one zapisane w ramach kolejnych, specjalnie zindeksowanych stron. Jak widać, w tym przypadku wielkość stro-ny będzie determinować liczbę generowa-nych fragmentów drzewa dla danego doku-mentu XML.

Listing 4. Schemat XML definiujący nasze zadania

Plik: Zadanie.xsd

<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"

targetNamespace="http://mmolak.pl/Zadanie">

<xsd:element name="zadanie">

<xsd:complexType>

<xsd:sequence>

<xsd:element name="nazwa" type="xsd:string" minOccurs="1" maxOccurs="1"/>

<xsd:element name="opis" type="xsd:string" minOccurs="1" maxOccurs="1"/>

<xsd:choice minOccurs="0" maxOccurs="1">

<xsd:sequence>

<xsd:annotation>

<xsd:documentation>Dla spotkań</xsd:documentation>

</xsd:annotation>

<xsd:element name="godzinaStart" type="xsd:time" minOccurs="1"

maxOccurs="1"/>

<xsd:element name="godzinaStop" type="xsd:time" minOccurs="1"

maxOccurs="1"/>

<xsd:element name="osoby" type="xsd:string" minOccurs="0"

maxOccurs="1"/>

</xsd:sequence>

<xsd:sequence>

<xsd:annotation>

<xsd:documentation>Dla artykułów</xsd:documentation>

</xsd:annotation>

<xsd:element name="magazyn" type="xsd:string" minOccurs="1"

maxOccurs="1"/>

</xsd:sequence>

</xsd:choice>

</xsd:sequence>

<xsd:attribute name="data" type="xsd:date" use="required"/>

<xsd:attribute name="typ" type="xsd:string" use="required"/>

</xsd:complexType>

</xsd:element>

</xsd:schema>

Listing 5. Wyzwalacz zapewniający automatyczną walidację

CREATE TRIGGER WalidujZadania NO CASCADE

BEFORE INSERT ON xmlapp.zadania

REFERENCING NEW AS nowe

FOR EACH ROW MODE db2sql

WHEN (nowe.zadanie IS NOT VALIDATED ACCORDING TO XMLSCHEMA ID XMLAPP.ZADANIE)

SET nowe.zadanie = XMLVALIDATE(nowe.zadanie ACCORDING TO XMLSCHEMA ID

XMLAPP.ZADANIE);

Page 44: Sdj Extra 35 Db2

44

Tworzenie aplikacji

SDJ Extra 35

W wersji 9.5 DB2 rozszerzono możli-wości silnika baz danych związane z za-pisem XML, wprowadzając tryb INLINE (patrz Rysunek 1). Rozwiązanie to pozwo-liło na zapis małych dokumentów XML na poziomie rekordu relacyjnego razem z in-nymi danymi. Z drugiej strony zachowują one w pełni hierarchiczny charakter. Nie-wątpliwymi zaletami trybu INLINE w wer-sji 9.5 są: zmniejszenie operacji odczytu i zapisu oraz wykorzystanie istniejących w DB2 mechanizmów głębokiej kompresji dla danych XML. Warto jednak dodać, iż w przypadku wersji 9.7 mechanizmy zo-

stały rozszerzone także na repozytorium hierarchiczne.

Wykorzystajmy zatem powyższą wiedzę w przygotowaniu struktur danych dla na-szej aplikacji. Załóżmy, iż w przypadku da-nych relacyjnych będziemy ograniczać się do przechowywania danych osobowych dla użytkowników aplikacji, tj. imienia, nazwi-ska i adresu e-mail oraz automatycznie gene-rowanych identyfikatorów rekordów. Struk-tury hierarchiczne będą natomiast przecho-wywać opis zadań do wykonania, a także re-guły transformowania dokumentów. Przy-gotujmy zatem przestrzenie tabel i związa-

ne z nimi pule buforów, posługując się po-leceniami:

CREATE BUFFERPOOL bp4k SIZE 10000

PAGESIZE 4 K;

CREATE BUFFERPOOL bp16k SIZE 10000

PAGESIZE 16 K;

CREATE TABLESPACE relData PAGESIZE 4 K

BUFFERPOOL bp4k NO FILE SYSTEM

CACHING;

CREATE TABLESPACE xmlData PAGESIZE 16 K

BUFFERPOOL bp16k NO FILE SYSTEM

CACHING;

Mając na uwadze wielkości zapisywanych informacji dla danych relacyjnych, użyje-my stron o długości 4KB, a dla dokumen-tów XML – 16KB, które zapewnią nam wy-dajną obsługę danych. Kolejnymi krokami w stronę optymalizacji było wyłączenie bufo-rowania na poziomie systemu plików, a tak-że określenie większego rozmiaru puli bufo-rów przy ich tworzeniu.

Utwórzmy zatem tabele dla naszej aplikacji. Posługując się poleceniami z Listingu 1, two-rzymy kolejno tabele z informacjami o użyt-kownikach systemu (PRACOWNICY), ich zada-niach (ZADANIA) oraz dla reguł transforma-cji (XSLT). W przypadku dokumentów XML przechowujących zadania wykorzystujemy tryb INLINE, pozwalając, aby dokumenty o wielkości do 500 bajtów były przechowywa-ne na poziomie rekordu. Dodatkowo dla tabeli zadań uruchamiamy mechanizm kompresji.

Na koniec, korzystając z dobrych prak-tyk IBM, optymalizujemy konfigurację ba-zy danych, zmieniając wartości parametrów APPLHEAPSZ i (CATALOGCACHE_SZ), używa-jąc poleceń:

UPDATE DB CFG USING APPLHEAPSZ 40000

AUTOMATIC;

UPDATE DB CFG USING CATALOGCACHE_SZ

100000;

W ten sposób zakończyliśmy pierwszy etap w przygotowaniu szkieletu aplikacji.

Umieszczanie dokumentów XML w bazie danychNadszedł czas na uzupełnienie naszej bazy danych. Naszą tabelę użytkowników syste-mu zasilimy w prosty sposób, posługując się poleceniem:

insert into XMLAPP.PRACOWNICY(imie,

nazwisko, email) values

('Marcin', 'Molak',

'[email protected]'),

('Artur', 'Wroński',

'[email protected]');

Czas na przygotowanie zadań. W naszym wypadku będą to informacje dotyczące pra-

Listing 6. Nowy schemat XML definiujący nasze zadania

Plik: Zadanie1.xsd

<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"

targetNamespace="http://mmolak.pl/Zadanie">

<xsd:element name="zadanie">

<xsd:complexType>

<xsd:sequence>

<xsd:element name="nazwa" type="xsd:string" minOccurs="1" maxOccurs="1"/>

<xsd:element name="opis" type="xsd:string" minOccurs="1" maxOccurs="1"/>

<xsd:choice minOccurs="0" maxOccurs="1">

<xsd:sequence>

<xsd:annotation>

<xsd:documentation>Dla spotkań</xsd:documentation>

</xsd:annotation>

<xsd:element name="godzinaStart" type="xsd:time" minOccurs="1"

maxOccurs="1"/>

<xsd:element name="godzinaStop" type="xsd:time" minOccurs="1"

maxOccurs="1"/>

<xsd:element name="osoby" type="xsd:string" minOccurs="0"

maxOccurs="1"/>

</xsd:sequence>

<xsd:sequence>

<xsd:annotation>

<xsd:documentation>Dla artykułów</xsd:documentation>

</xsd:annotation>

<xsd:element name="magazyn" type="xsd:string" minOccurs="1"

maxOccurs="1"/>

</xsd:sequence>

<xsd:sequence>

<xsd:annotation>

<xsd:documentation>Dla telefonów</xsd:documentation>

</xsd:annotation>

<xsd:element name="godzina" type="xsd:time" minOccurs="1"

maxOccurs="1"/>

<xsd:element name="rozmowca" type="xsd:string" minOccurs="0"

maxOccurs="1"/>

</xsd:sequence>

</xsd:choice>

</xsd:sequence>

<xsd:attribute name="data" type="xsd:date" use="required"/>

<xsd:attribute name="typ" type="xsd:string" use="required"/>

</xsd:complexType>

</xsd:element>

</xsd:schema>

Page 45: Sdj Extra 35 Db2

<XML> w praktyce

45www.sdjournal.org

cy nad niniejszym artykułem – czyli zdefi-niowanie jego tematyki, oraz spotkania ro-bocze. Treść kolejnych dokumentów przed-stawia Listing 2.

Jak łatwo zauważyć, każde z zadań po-siada pewne stałe elementy, takie jak atry-buty data i typ oraz elementy nazwa i opis. Jednak każdy z typów zadań posiada ele-menty unikalne dla siebie. Co więcej, nie-które z nich są opcjonalne. Możemy zatem korzystać w pełni z uniwersalności języ-ka XML.

Aby każdy z użytkowników mógł wy-godnie dodać zadanie, przygotujemy pro-cedurę o nazwie DODAJZADANIE, w której każdy z użytkowników jako parametry wej-ściowe poda swoje id oraz dokument XML z opisem zadania. W wyniku zaś otrzyma id, które otrzymało w bazie wstawiane za-danie. Analiza ciała procedury (patrz Li-sting 3) pokazuje, iż cała operacja wsta-wiania ogranicza się do przekazania war-tości zmiennych wejściowych i użycia kla-sycznej komendy INSERT. Wywołanie pro-cedury sprowadza się zaś do wykonania ko-mendy:

CALL XMLAPP.DODAJZADANIE (1,

XMLPARSE(DOCUMENT

'treść naszego dokumentu XML’),?);

gdzie 1 jest numerem wybranego pracow-nika (w naszym przykładzie Artura Wroń-skiego).

W ten sposób przygotowana procedura może być wywołana z dowolnego języka pro-gramowania. Możliwe jest zatem przygoto-wanie aplikacji, która zadanie wczyta z pliku i zapisze je do bazy danych dla danego użyt-kownika. Funkcjonalność taką oferuje rów-nież narzędzie Data Studio Developer, wspo-magające proces przygotowania aplikacji ba-zodanowych.

My w ramach ćwiczenia dodajmy infor-macje o spotkaniach do obydwu pracowni-ków, a zadanie typu artykuł jedynie do je-go autora.

Walidacja danychJednak czy w ten sposób przygotowane roz-wiązanie zapewni nam poprawność wpro-wadzonych przez użytkownika danych? Aby odpowiedzieć sobie na to pytanie, musimy uszczegółowić zagadnienie poprawności do-kumentów XML.

Podstawowym poziomem poprawności dokumentów jest poprawność składniowa. Nakłada ona na dostawcę dokumentu szereg zasad budowy określonych w ramach spe-cyfikacji. Niespełnienie ich powoduje błę-dy analizatora składniowego i brak możli-wości opracowania hierarchicznej struktury w bazie. W takich sytuacjach próba dodania dokumentu nie powiedzie się, a DB2 zgłosi

komunikat z informacją o nieprawidłowym dokumencie.

Drugim poziomem poprawności jest po-prawność strukturalna wymagająca zdefinio-wania dodatkowych reguł walidacji. Reguły takie gwarantują nam poprawność informa-cji w kontekście biznesowym, jednak wyma-gają użycia specjalnego języka. W przypad-

ku DB2, w ślad za standardami, jest to XML Schema.

Przykładem dokumentu XML Schema dla zadań wstawianych przez naszą aplika-cję jest Listing 4. Widzimy, iż precyzuje on kolejność występowania elementów, wyróż-nia elementy wspólne (nazwa, opis), a tak-że elementy charakterystyczne dla danego

Listing 7. Zapytanie SQL/XML z automatycznym rzutowaniem

select z.id, z.prac_id, t.*

from xmlapp.zadania z,

xmltable (

XMLNAMESPACES('http://mmolak.pl/Zadanie' AS "p"),

'$ZADANIE/p:zadanie'

COLUMNS

dzien_zadania DATE PATH './@data',

nazwa VARCHAR(3200) PATH './nazwa',

opis VARCHAR(3200) PATH './opis'

) as t

WHERE

xmlexists(

'declare namespace p="http://mmolak.pl/Zadanie";

$ZADANIE/p:zadanie[@typ="spotkanie"]')

ORDER BY z.prac_id;

Listing 8. Użycie funkcji XMLGROUP

SELECT XMLGROUP( IMIE AS "Imie",

NAZWISKO AS "Nazwisko",

EMAIL AS "E-mail",

OPTION ROW "Pracownik" ROOT "Pracownicy")

AS Lista

FROM XMLAPP.PRACOWNICY;

LISTA

----------

<Pracownicy>

<Pracownik>

<Imie>Marcin</Imie>

<Nazwisko>Molak</Nazwisko>

<E-mail>[email protected]</E-mail>

</Pracownik>

<Pracownik>

<Imie>Artur</Imie>

<Nazwisko>Wroński</Nazwisko>

<E-mail>[email protected]</E-mail>

</Pracownik>

</Pracownicy>

Listing 9. Wyrażenie FLWOR zawracające listę spotkań

xquery

declare default element namespace 'http://mmolak.pl/Zadanie';

<spotkania>{

for $i in db2-fn:sqlquery("SELECT ZADANIE FROM XMLAPP.ZADANIA WHERE prac_id=1")

let $x := $i/zadanie

where $x/@typ="spotkanie"

order by $x/@data, $x/godzinaStart

return $x

}</spotkania>

Page 46: Sdj Extra 35 Db2

46

Tworzenie aplikacji

SDJ Extra 35

typu zadań. Pozwala także na określenie ty-pu danych oraz ilości elementów o danej na-zwie w ramach dokumentu XML. Podob-ne zasady dotyczą również atrybutów dane-go elementu.

Aby zarejestrować schemat XML, wykonu-jemy polecenie:

REGISTER XMLSCHEMA

'http://mmolak.pl/Zadanie'

FROM

'file:////sciezka_do_pliku\Zadanie.xsd'

AS XMLAPP.ZADANIE COMPLETE;

Od tej chwili możliwa jest walidacja doku-mentu XML. Zmodyfikujmy więc nieco kod naszej procedury, wykorzystując funk-cję walidacji dokumentów – XMLVALIDATE. Dotychczasowe polecenie INSERT zastępuje-my następującym:

INSERT INTO XMLAPP.ZADANIA(

PRAC_ID, ZADANIE )

VALUES(DODAJZADANIE.PRACOWNIK,

XMLVALIDATE(DODAJZADANIE.ZADANIE

ACCORDING TO XMLSCHEMA

ID XMLAPP.ZADANIE) )

Po ponownym wdrożeniu procedury struk-tura każdego nowego dokumentu będzie sprawdzana na podstawie schematu, które-go nazwę w bazie danych określiliśmy w mo-mencie rejestracji.

Jednak w takim wypadku gwarancja popraw-ności dokumentu jest zapewniona lokalnie dla naszej procedury składowanej. W DB2 istnieją jednak mechanizmy, które pozwalają nam na za-pewnienie strukturalnej poprawności na pozio-mie globalnym. Pierwszy z nich stanowi waru-nek spójności (ang. constraint), w którym doko-nujemy sprawdzenia, czy walidacja danego do-kumentu została wykonana w oparciu o sche-mat XML. Aby założyć taki warunek dla naszej tabeli zadań, posługujemy się poleceniem:

ALTER TABLE xmlapp.zadania

ADD CONSTRAINT wZadan

CHECK(zadanie IS VALIDATED ACCORDING TO

XMLSCHEMA ID XMLAPP.ZADANIE);

Należy jedynie zauważyć, iż mechanizm ten nie powoduje automatycznej walidacji dokumen-tu. Aby zapewnić taką funkcjonalność, od wer-sji 9.5 możemy posłużyć się wyzwalaczem (ang. trigger), który przed zapisaniem dokumentu do bazy dokona sprawdzenia poprawności struk-turalnej. Kod takiego wyzwalacza dla naszej aplikacji przedstawiono na Listingu 5.

Nasze aplikacje podlegają jednak ciągłym modyfikacjom wynikającym z nowych wy-magań biznesowych. Nie trudno wyobrazić sobie sytuację, w której ktoś chciałby dodać do listy swoich zadań wykonywanie telefo-nów. W takiej sytuacji istniejący schemat mógłby ograniczyć możliwość wprowadza-nia informacji tego typu. Odpowiedzią na te zagadnienie jest wprowadzona w wersji 9.5 ewolucja schematów XML.

Aby skorzystać z tego mechanizmu, przy-gotowujemy nowy schemat (Listing 6) i reje-strujemy go w bazie danych poleceniem:

REGISTER XMLSCHEMA

'http://mmolak.pl/Zadanie1'

FROM

'file:////sciezka_do_pliku\Zadanie1.xsd'

AS XMLAPP.ZADANIE1 COMPLETE;

Ostatnim krokiem jest aktualizacja schema-tu poprzez wywołanie procedury składowa-nej XSR _ UPDATE:

CALL SYSPROC.XSR_UPDATE('XMLAPP','ZADANIE'

,'XMLAPP','ZADANIE1',0);

Od tego momentu nasze dokumenty będą walidowane nowym schematem. Należy jed-nak zwrócić uwagę na pewne ograniczenia takiego podejścia. Nowy schemat nie może bowiem wprowadzić zmian, które zawiera-łyby reguły niespełniane przez istniejące już w bazie dokumenty XML. Niemożliwe są

Listing 10. Procedura dodająca nowe osoby do spotkania

CREATE PROCEDURE XMLAPP.DODAJOSOBY ( IN ZAD_ID BIGINT, IN OSOBY VARCHAR(255) )

P1: BEGIN

DECLARE istnieja INT;

DECLARE cursor1 CURSOR FOR

SELECT COUNT(id) FROM xmlapp.zadania z

WHERE z.id=dodajosoby.zad_id

AND XMLEXISTS(

'declare namespace p="http://mmolak.pl/Zadanie";

$ZADANIE/p:zadanie/osoby'

);

OPEN cursor1;

FETCH cursor1 INTO istnieja;

CLOSE cursor1;

IF istnieja = 0 THEN

UPDATE xmlapp.zadania

SET zadanie = XMLQUERY(

'declare namespace p="http://mmolak.pl/Zadanie";

copy $new := $ZADANIE

modify do insert <osoby>{$osoby}</osoby>

after $new/p:zadanie/godzinaStop

return $new'

PASSING dodajosoby.osoby AS "osoby"

) WHERE id=dodajosoby.zad_id

AND XMLEXISTS(

'declare namespace p="http://mmolak.pl/Zadanie";

$ZADANIE/p:zadanie[@typ="spotkanie"]'

);

ELSE

UPDATE xmlapp.zadania

SET zadanie = XMLQUERY(

'declare namespace p="http://mmolak.pl/Zadanie";

copy $new := $ZADANIE

modify do replace value of $new/p:zadanie/osoby

with $ZADANIE/p:zadanie/osoby+ " "+$osoby

return $new'

PASSING dodajosoby.osoby AS "osoby"

) WHERE id=dodajosoby.zad_id

AND XMLEXISTS(

'declare namespace p="http://mmolak.pl/Zadanie";

$ZADANIE/p:zadanie[@typ="spotkanie"]'

);

END IF;

END P1

@

Page 47: Sdj Extra 35 Db2

<XML> w praktyce

47www.sdjournal.org

zatem zmiany typu danych dla elementów czy dodawanie nowych, obowiązkowych elementów czy atrybutów.

Nowy widok na daneWprowadzenie obsługi dokumentów XML wymagało również implementacji nowego interfejsu dostępu do danych uwzględniają-cego ich hierarchiczną strukturę i pozwalają-cego na jej łatwe przeszukiwanie. W tej mate-rii naturalnym wyborem stał się język XQu-ery. Z drugiej strony należało zachować pełną możliwość dostępu do danych z poziomu ję-zyka SQL. Mając jednak na uwadze jego ogra-niczenia związane z przetwarzaniem doku-mentów XML, IBM zdecydował się na imple-mentację specyfikacji SQL/XML wzbogacają-cej składnie o nowe funkcje pozwalające na mapowanie danych.

Bardzo ważnym elementem dla modelu hy-brydowego stała się jednak nie tylko imple-mentacja obu języków, ale także możliwości ich wzajemnej integracji. Dzięki temu możli-we stało się definiowanie zapytań SQL z ele-mentami XQuery, a także zapytań XQuery z osadzonymi poleceniami SQL. W wersji 9.1 dla wielu funkcji SQL/XML wymagane by-

ło jawne rzutowanie parametrów między dia-lektami za pomocą klauzuli PASSING defi-niującej zmienną SQL i odpowiadający jej alias XML. W wersji 9.5 wprowadzono automa-tyczne rzutowanie, minimalizując użycie klau-zuli do specyficznych przypadków. Przykład automatycznego rzutowania ilustruje Listing 7, w którym nazwa kolumny XML jest użyta w wyrażeniach XQuery jako nazwa zmiennej.

Kod ten obrazuje jednocześnie prostą de-kompozycję dokumentów XML do struk-tur relacyjnych na bazie określonych węzłów drzewa. Przy okazji warto zwrócić uwagę na funkcję XMLEXISTS, która stanowi właściwy sposób definiowania warunków wyboru do-kumentów XML z kolekcji.

Język SQL/XML pozwala również na bu-dowanie dokumentów XML na bazie da-nych relacyjnych. W wersji 9.1 zrealizowa-nie tego celu wymuszało na programistach zastosowanie szeregu funkcji, takich jak: XMLDOCUMENT, XMLELEMENT czy XMLAGG. W wersji 9.5 do SQL/XML dołączono XML-ROW zapisującą rekord w postaci dokumen-tu XML, oraz XMLGROUP budującą doku-ment XML na podstawie zbioru rekordów. Na Listingu 8 przedstawiono przykładowe

użycie funkcji XMLGROUP z uzyskanym wynikiem.

Dla języka XQuery integracja z SQL została oparta na dwóch funkcjach: db2-fn:xmlcolumn oraz db2-fb:sqlquery. Pozwoliły one na mapo-wanie kolumny typu XML stanowiącej ele-ment tabeli bądź wynik zapytania SQL na ko-lekcje XML. Umożliwiło to przetwarzanie do-kumentów w ich oryginalnej postaci do za-danego zbioru wynikowego za pomocą wyra-żeń. Najczęściej stosowanym jest wyrażenie FLWOR. Jego definicję stanowią człony: for – umożliwiający iteracje po elementach zbioru, let – definiujący zmienne, where – określający warunki brzegowe, order – sortujący zbiór, oraz return – zwracający zbiór wynikowy. Przykłado-we użycie ilustruje Listing 9. Na podstawie ze-stawu zadań pracownika z id równym 1 genero-wany jest dokument XML zawierający informa-cje o kolejnych spotkaniach.

Modyfikacja dokumentów XMLZ punktu widzenia aplikacji ważnym ele-mentem w obsłudze danych jest jednak nie tylko możliwość ich przeszukiwania, ale rów-nież modyfikacji. W przypadku wersji 9.1 funkcjonalność taka nie stanowiła elementu

Listing 11. Reguły transformujące dokument XML do formatu HTML

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/

Transform"

xmlns:p="http://mmolak.pl/Zadanie"

xmlns:xsi="http://www.w3.org/2001/

XMLSchema-instance"

version="1.0"

xmlns:xalan="http://xml.apache.org/xslt">

<xsl:template match="zadania">

<html>

<head>

<title>Zadania dla pracownika</title>

</head>

<body>

<table>

<th>

<td>Data</td>

<td>Typ</td>

<td>Nazwa zadania</td>

<td>Opis</td>

</th>

<xsl:apply-templates />

</table>

</body>

</html>

</xsl:template>

<xsl:template match="p:zadanie"

xsi:schemaLocation="http://mmolak.pl/Zadanie

Zadanie.xsd">

<tr>

<td><xsl:value-of select="./@data" /></td>

<td><xsl:value-of select="./@typ" /></td>

<td><xsl:value-of select="./nazwa" /></td>

<td><xsl:value-of select="./opis" /></td>

</tr>

</xsl:template>

</xsl:stylesheet>

Listing 12. Procedura zwracająca raport z zadaniami pracownika

CREATE PROCEDURE XMLAPP.POKAZRAPORT ( IN ID_PRAC BIGINT, IN TYP

CHAR(5) DEFAULT 'html ')

DYNAMIC RESULT SETS 1

P1: BEGIN

DECLARE xsltDoc XML;

DECLARE cursor CURSOR FOR

SELECT DOKUMENT FROM XMLAPP.XSLT WHERE

NAZWA=POKAZRAPORT.TYP;

DECLARE cursor1 CURSOR WITH RETURN FOR

WITH wynik(dokument) AS (

SELECT XMLELEMENT(

NAME "zadania",

XMLAGG(

ZADANIE

)

) FROM XMLAPP.ZADANIA WHERE PRAC_ID =

POKAZRAPORT.ID_PRAC

)

SELECT xsltransform(dokument USING xsltDoc AS clob(10M) )

FROM wynik;

OPEN cursor;

FETCH cursor INTO xsltDoc;

CLOSE cursor;

OPEN cursor1;

END P1

@

Page 48: Sdj Extra 35 Db2

48

Tworzenie aplikacji

SDJ Extra 35

motoru bazy danych. Można ją było osiągnąć jedynie poprzez dodanie upublicznionych procedur składowanych bądź własne imple-mentacje na poziomie warstwy aplikacji.

Wraz z wprowadzeniem na rynek wersji 9.5 w motorze bazy danych rozszerzono obsługę składni języka XQuery o klauzulę transform co-py, pozwalającą na modyfikowanie dokumen-tu XML. Użytkownikom umożliwiono:

• zmianę wartości węzła;• zastąpienie węzła nowym;• dodanie nowego węzła z uwzględnie-

niem jego położenia w dokumencie;• skasowanie węzła;• zmianę nazwy węzła;• jednoczesną modyfikację wielu węzłów

w ramach jednej instrukcji UPDATE;• jednoczesną modyfikację wielu doku-

mentów w ramach jednej instrukcji UPDATE.

Aby zademonstrować możliwość aktualiza-cji dokumentów XML, stworzymy procedu-rę DODAJOSOBY, która pozwoli użytkowni-kowi dla danego spotkania dodać nowe osoby.

Analizując Listing 10, widzimy, iż proces prze-biega dwuetapowo. Na wstępie sprawdzane jest występowanie elementu osoby w drzewie doku-mentu XML. Jest to zabezpieczenie przed pró-bą modyfikacji nieistniejącego elementu, która zakończyłaby się błędem. W przypadku braku węzła jest on dodawany w pierwszej instrukcji UPDATE. Mając na uwadze poprawność struk-turalną (opisaną w schemacie XML), element osoby zostaje umieszczony po elemencie godzi-naStop. Jeśli węzeł istnieje, jego wartość jest za-stępowana nową, stanowiącą konkatenację ist-niejącego i wprowadzonego przez użytkowni-ka łańcucha znaków. Drugim zabezpieczeniem przed nieprawidłowym działaniem użytkow-nika jest również wykorzystanie warunku defi-niującego typ zadania.

Transformacja dokumentów XMLKolejną funkcjonalnością, wprowadzoną w wersji 9.5, jest możliwość transformacji doku-mentów XML do innych formatów,takich jak XHTML, Open Document czy Open XML. Za jej realizację odpowiada funkcja XSLTRANSFORM wykorzystująca dokumenty z regułami zapisa-nymi w języku XSLT. bazującym na XML.

Wyobraźmy sobie, iż z naszej bazy za-dań chcemy wydrukować raport w formacie HTML. Raport ten ma zawierać tabelkę z pre-zentacją daty, typu, nazwy i opisu kolejnych zadań dla danej osoby. Ponieważ reguły trans-formujące są zapisane w dokumentach XML, możemy je zapisać w tabeli DB2. W tym ce-lu skorzystamy z przygotowanej na początku artykułu tabeli XSLT. Arkusz transformujący nasze dokumenty do strony HTML (patrz Li-sting 11) dodajemy poleceniem:

INSERT INTO XMLAPP.XSLT(NAZWA,DOKUMENT)

VALUES (‘html’,

XMLPARSE(DOCUMENT 'treść dokumentu

XSLT’));

Kolejny krok stanowi przygotowanie procedu-ry POKAZRAPORT (Listing 12), która dla danego id pracownika oraz typu transformaty zwró-ci nam w wyniku raport. Przeanalizujmy jej działanie. W pierwszym kroku do zmien-nej typu XML wczytywany jest wybrany ar-kusz transformat. Następnie na bazie wszyst-kich dokumentów zawierających zadania jest utworzony przy pomocy funkcji XMLELEMENT i XMLAGG jeden dokument XML z głównym węzłem zadania. Ten zaś jest transformowa-ny przez funkcje XSLTRANSFORM.

W przykładzie tym warto również zwrócić uwagę na definicję drugiego parametru wej-ściowego, w której określono jego wartość do-myślną. Jest to jedna z nowych funkcjonalności wprowadzonych w DB2 9.7. Zatem wywołanie dla transformaty HTML będzie miało postać:

CALL XMLAPP.POKAZRAPORT(1);

PodsumowanieWraz z wejściem na rynek wersji 9.1 przed pro-gramistami DB2 otworzyło się wiele nowych możliwości. Możliwości związanych ze składo-waniem, przetwarzaniem i transformowaniem dokumentów XML stanowiących standard w wielu współczesnych implementacjach.

Niniejszy artykuł stanowi początek drogi do poznania technologii pureXML. Przed czytel-nikiem pozostaje jeszcze wiele ciekawych za-gadnień, takich jak wykorzystanie XML w śro-dowiskach partycjonowanych, wielowymiaro-wych klastrach MDC oraz strukturach anali-tycznych (OLAP) czy dekompozycja XML na potrzeby przepływów danych ETL.

Rozbudowana funkcjonalność XML przy zachowaniu wysokiej wydajności bazy danychStale rosnący wolumen danych powoduje, iż firmy szukają nie tylko rozwiązań oferujących rozbudowaną funkcjonalność, ale przede wszystkim gwarantujących zachowanie stałego poziomu wydajności.Wykorzystując test TPoX (Transaction Processing over XML), firmy IBM i Intel sprawdziły wydaj-ność bazy DB2 dla 350 milionów dokumentów XML o łącznej wielkości 1TB. Symulowano rze-czywiste obciążenie opierające się w 70% na przeszukiwaniu dokumentów i 30% na opera-cjach dodawania, kasowania oraz modyfikowania.Środowisko testowe wykorzystywało 64 bitową platformę Linux z zainstalowanym serwerem danych DB2 9.5 z 2 pakietem poprawek. Serwer był wyposażony w 4 sześciordzeniowe pro-cesory z rodziny Intel Xeon 7400 2.67GHz, 64GB pamięci RAM i 137 dysków. Analizowana ba-za wykorzystywała strony o wielkości 16KB, a także mechanizmy automatycznego strojenia, zarządzania pamięcią i rozszerzania obszaru dyskowego. Tabele dokumentów XML wykorzy-stywały tryb INLINE i kompresję danych.Wynik wyprzedził wszystkie oczekiwania. Po załadowaniu dane zajmowały powierzchnię 440,2 GB, wskazując maksymalny poziom kompresji na poziomie 64%. Natomiast w teście obciążenia baza osiągnęła liczbę 6763 transakcji na sekundę.Więcej o teście pod adresem: tpox.sourceforge.net/tpoxdata_files/Taming_1TB_of_XML_Da-ta_with_DB2+Intel.pdf.

8,000

7,000

6,000

5,000

4,000

3,000

2,000

1,000

0

0 50 100 150 200 250

TT

PS

TPoX Transactions per Seckond (TTPS)

Number of concurrent users

6,763 TTPS at 95% CPU

TTPS

MARCIN MOLAKSpecjalista w zakresie zarządzania danymi w dziale oprogramowania IBM Polska i ambasa-dor programu inicjatywy akademickiej IBM. Od-powiada za wsparcie serwerów danych DB2 i so-lidDB oraz narzędzi z rodzin Optim i Data Stu-dio. Absolwent Wydziału Fizyki Politechniki War-szawskiej. Prywatnie pasjonat nowych technolo-gii oraz miłośnik fantastyki.

Page 49: Sdj Extra 35 Db2

49www.sdjournal.org

Opis CD

IBM DB2 9.7 Discovery Kit stanowi bogaty zestaw materiałów technicznych, pozwalających na poszerzenie wiedzy w zakresie ser-wera baz danych DB2. Na płycie znajdują się książki, artykuły, prezentacje oraz filmy instruktażowe dedykowane administratorom baz danych, dewelope-

rom aplikacji oraz architektom IT. Nowe funkcjonalności serwera danych możesz przetestować dzięki załączonym: wersji testowej DB2 Enterprise Edition oraz przygotowanemu środowisku wirtualnemu – DB2 Enterprise Virtual Appliance.

Materiały obejmują również informacje o ekosystemie DB2 – hurtowni InfoSphere Warehouse, rodzinie narzędzi Optim oraz bazie danych, rezydującej w pamięci – solidDB. Znajdziesz tam również materiały na temat ścieżek certyfikacyjnych oraz programów part-nerskich i akademickich.

DB2 9.7 Discovery Kit

Page 50: Sdj Extra 35 Db2

50

Tworzenie aplikacji

SDJ Extra 35

P rzybliżymy także optymistyczne blokowanie rekordów (ang. optimi-stics locking) wprowadzone w wer-

sji 9.5 oraz nowy tryb izolacji wykorzystu-jący wersjonowanie rekordu wprowadzony w wersji 9.7.

Obsługa blokadDB2 przechowuje informacje o blokadach w pamięci operacyjnej w tzw. stercie blo-kad (ang. lock heap). Każda baza danych posiada własną stertę blokad, a jej rozmiar określony jest przez parametr bazy LOC-KLIST. Jeśli aplikacja potrzebuje zabloko-wać dany rekord, wtedy informacja o bloka-dzie odkładana jest na liście blokad. Bloka-dy są zakładane nie tylko dla każdego zmo-dyfikowanego rekordu, ale także dla odczy-tanych wierszy, zależnie od poziomu izola-cji zapytania.

W konfiguracji baz danych DB2 są dwa parametry regulujące liczbę blokad, które może otrzymać pojedyncza aplikacja. Są to LOCKLIST i MAXLOCKS. Pierwszy z nich został już przedstawiony i definiuje ob-szar pamięci przeznaczony do przechowy-wania informacji o blokadach dla wszyst-kich aplikacji łączących się do bazy da-nych. Drugi z nich mówi, jaka część te-go obszaru może być maksymalnie przy-dzielona dla pojedynczej aplikacji. Para-metr LOCKLIST wyrażony jest w 4 kilobaj-towych stronach, natomiast MAXLOCKS w procentach.

We wcześniejszych wersjach DB2 (do wersji 8 włącznie) do obowiązków admini-stratora należało ustawienie wartości tych parametrów, tak by zarezerwować pamięć dla odpowiedniej liczby blokad. Ustawie-nie tych parametrów na następujące war-tości:

• LOCKLIST = 100;• MAXLOCKS = 22.

oznacza, że na blokady dla wszystkich apli-kacji łączących się do tej bazy danych jest przeznaczone 100 x 4 KB = 400 KB pa-mięci, z czego pojedyncza aplikacja łączą-ca się do bazy danych może maksymalnie wykorzystać 22%, czyli 88 KB. Do opisu jednej blokady w instancji 32 bitowej DB2 potrzebne są 32 bajty, natomiast w instan-cji 64 bitowej jedna blokada zajmuje 72 baj-ty. Można łatwo obliczyć, jaka jest maksy-malna liczba blokad dostępna dla pojedyn-czej aplikacji przy takiej konfiguracji ba-zy danych:

• 11378 blokad w 32 bitowej instancji;• 5689 blokad w 64 bitowej instancji.

Domyślnie DB2 przydziela blokady na po-ziomie pojedynczego rekordu. Jeśli aplika-cja wykorzysta wszystkie przysługujące jej blokady, wtedy dalsza kontynuacja pracy aplikacji możliwa jest dopiero po wykona-niu tzw. eskalacji blokad. Mechanizm ten wyzwalany jest automatycznie i powodu-je, że serwer DB2 na czas trwania transak-cji zamienia wiele blokad na poziomie re-kordu jedną blokadą na poziomie tabeli. Ta-kie zachowanie pozwala zmniejszyć liczbę blokad używanych przez aplikację, ale jed-nocześnie ogranicza współbieżność dostę-pu do tabeli dla innych aplikacji. Inne apli-kacje będą musiały czekać aż sesja będąca przyczyną eskalacji blokad zatwierdzi bądź wycofa bieżącą transakcję. Zadaniem admi-nistratora baz danych DB2 w wersji 8 było dobranie tych parametrów w zależności od liczby wierszy w tabelach, dostępnej pamię-ci operacyjnej oraz rozmiaru transakcji, tak aby eskalacja blokad nie występowała. Od wersji DB2 9 dla nowo tworzonych baz da-nych te dwa parametry domyślnie ustawio-ne są na wartość AUTOMATIC i serwer DB2 dynamicznie ustawia te dwa parame-try w zależności od potrzeb.

Poziomy izolacjiLiczba blokad, którą serwer zaalokuje dla da-nej aplikacji, zależy nie tylko od wykonywa-nych modyfikacji danych. Zapytania także mogą zakładać blokady na rekordach, a licz-ba blokad zależy od wybranego poziomu izo-lacji.

DB2 obsługuje następujące poziomy izo-lacji:

• UNCOMMITTED READ (UR) – od-czyt niezatwierdzonych danych;

• CURSOR STABILITY (CS) – stabilność kursora;

• READ STABILITY (RS) – stabilność odczytu;

• REPEATABLE READ (RR) – odczyt powtarzalny.

Poziomy izolacji mają za zadanie odizolo-wać wpływ innych aplikacji na przepro-wadzane operacje na bazie danych, tak by zapewnić spójność wykonywanych opera-cji. Każdy z czterech dostępnych pozio-mów izolacji oferuje różny poziom odse-parowania wpływu działania innych apli-kacji. Działanie poziomów izolacji zapre-zentujemy na prostej tabeli rezerwacje, którą utworzyliśmy następującą instruk-cją SQL:

create table rezerwacje (

data date,

dzien char(13),

id_osoby int

)

Tabelę REZERWACJE wypełnimy datami z za-kresu od stycznia 2009 do grudnia 2010 przy pomocy złożonej instrukcji SQL poka-zanej na Listingu 1 (wywołanie: db2 –td@ -f plik _ z _ instrukcją). Poniżej przedsta-wiliśmy kilka początkowych rekordów zała-dowanych do tej tabeli:

Poziomy izolacji w DB2Deweloperom oprogramowania, którzy dopiero rozpoczynają korzystanie z DB2, stosunkowo dużą trudność sprawia zrozumienie i poprawne wykorzystanie poziomów izolacji, tak by zapewnić maksymalną współbieżność aplikacji. W tym artykule omówimy te mechanizmy, co, mamy nadzieję, pozwoli tworzyć bardziej wydajne aplikacje.

Artur Wroński, Krzysztof Mikołajewski

Page 51: Sdj Extra 35 Db2

Poziomy izolacji w DB2

51www.sdjournal.org

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-01-01 czwartek -

2009-01-02 piątek -

2009-01-03 sobota -

2009-01-04 niedziela -

Pole ID _ OSOBY początkowo zawiera warto-ści NULL, które będziemy sukcesywnie wy-pełniać identyfikatorami osób dokonujących rezerwacji.

UNCOMMITTED READPoziom izolacji UNCOMMITTED READ (UR) nie korzysta z blokad i odczytuje zawsze bieżą-cą wartość. Zademonstrujemy działanie te-go poziomu izolacji, uruchamiając dwie se-sje interpretera poleceń DB2 podłączone do wybranej bazy danych. Sesje interprete-ra uruchomimy z wyłączonym automatycz-nym zatwierdzaniem. Wszystkie przykłady podane w artykule będą wymagały jawne-go zatwierdzenia (COMMIT) bądź wycofania transakcji (ROLLBACK). W tym celu w oknie interpretera DB2 przed podłączeniem się do bazy danych ustawimy zmienną środowisko-wą DB2OPTIONS=+c. Następującym po-leceniem możemy sprawdzić, czy automa-tyczne zatwierdzanie jest wyłączone (au-to-commit off):

db2 list command options

Command options (DB2OPTIONS) = +c

Option Description Current Setting

------ --------------- ---------------

-a Display SQLCA OFF

-c Auto-Commit OFF

W pierwszej sesji uaktualnimy ID _ OSOBY dla rekordu o dacie 2009-09-15. Ponieważ wyłączyliśmy automatyczne zatwierdzanie, po wykonaniu poniższej instrukcji na uak-tualnianym rekordzie zostanie utrzymana blokada na wyłączność (X):

db2 update rezerwacje set id_osoby = 120

where data = '2009-09-15'

Z drugiej sesji spróbujemy odczytać modyfi-kowany rekord w trybie izolacji UR. Poziom izolacji można określić dla każdego zapy-tania poprzez dodanie na końcu zapytania klauzuli WITH:

db2 select * from rezerwacje where data =

'2009-09-15' with ur

Zapytanie zwróci aktualną, jeszcze nieza-twierdzoną wartość:

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-09-15 wtorek 120

Jak widać, poziom izolacji UR odczytuje nie-zatwierdzone dane i dlatego wymaga uważ-nego stosowania. Aplikacja modyfikująca re-kord może wycofać zmiany (ROLLBACK), co może oznaczać, że druga sesja odczyta war-tość, która tak naprawdę nigdy nie została trwale zapisana w bazie danych.

Poziom izolacji można także ustawić in-strukcją SET ISOLATION:

db2 set isolation = UR

Po wykonaniu powyższej instrukcji wszystkie instrukcje SQL, dla których jawnie nie podaliśmy poziomu izolacji, bę-dą stosowały poziom UR. Sięgając do reje-stru CURRENT ISOLATION, możemy

sprawdzić domyślny poziom izolacji w bie-żącej sesji:

db2 values current isolation

1

--

UR

1 record(s) selected.

CURSOR STABILITYZ definicji poziom izolacji CURSOR STABILITY (CS) czyta wyłącznie zatwierdzone dane. Je-śli w drugiej sesji wykonam to samo zapyta-nie z tym poziomem izolacji, wtedy mam gwarancję, że nie odczytamy już niezatwier-

Listing 1. Instrukcja ładująca tabelę rezerwacje

BEGIN ATOMIC

DECLARE data DATE;

SET data = DATE('2009-01-01');

WHILE ( data < DATE('2011-01-01') )

DO

INSERT INTO rezerwacje VALUES (data, dayname(data),NULL);

SET data = data + 1 DAY;

END WHILE;

END @

Listing 2. Usunięcie powtarzających rekordów

insert into rezerwacje values ('2010-11-30', dayname('2010-11-30'), 99) ;

select * from rezerwacje where data = '2010-11-30'

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2010-11-30 wtorek 99

2010-11-30 wtorek -

select * from old table (

delete from (

select row_number() over (

partition by DATA order by ID_OSOBY asc) as nr

from rezerwacje

) where nr > 1

) ;

DATA ID_OSOBY

---------- -----------

2010-11-30 -

select * from rezerwacje where data = '2010-11-30'

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2010-11-30 wtorek 99

Page 52: Sdj Extra 35 Db2

52

Tworzenie aplikacji

SDJ Extra 35

dzonych rekordów, tak jak w przypadku po-ziomu UR.

db2 select * from rezerwacje where data =

'2009-09-15' with cs

Zachowanie sesji będzie jednak zależało od użytej wersji DB2. W DB2 9.5 i wcze-śniejszych wersjach zapytanie to zostanie wstrzymane aż do momentu, kiedy aplika-cja z pierwszej sesji zatwierdzi bądź wycofa transakcję. Zapytanie będzie czekać okre-śloną liczbę sekund zdefiniowaną przez pa-rametr bazy danych LOCKTIMEOUT. Jeśli po tym czasie aplikacja z pierwszej sesji nie zwolni blokady, wtedy zapytanie zakończy się błędem:

SQL0911N The current transaction has been

rolled back because of a deadlock

or timeout. Reason code "68".

SQLSTATE=40001

Domyślnie parametr LOCKTIMEOUT ustawio-ny jest na wartość -1, co oznacza, że apli-kacja będzie czekała dowolnie długo. Czas oczekiwania na zwolnienie blokady można określić także instrukcją SET CURRENT LOCK TIMEOUT. Wykonanie poniższej instrukcji spowoduje, że w bieżącej sesji czas oczeki-wania na zwolnienie zasobu będzie wyno-sił 15 sekund:

db2 set current lock timeout 15

W DB2 9.7 zapytanie z poziomem izolacji CS nie będzie czekało na zwolnienie bloka-dy i od razu zwróci wynik. Jeśli dany rekord jest przetwarzany na wyłączność przez inną aplikację, wtedy zwracana jest poprzednia

zatwierdzona wartość rekordu, czyli w na-szym przypadku zapytanie zwróci:

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-09-15 wtorek -

Warto wspomnieć, że tak zachowa się za-pytanie, którego intencją jest wyłącznie od-czytanie danych (np. do celów raporto-wych), a nie modyfikacja danych. Wykona-nie zapytania z zamiarem zapisu (klauzula FOR UPDATE) spowoduje, że zapytanie tak-że zostanie wstrzymane na zablokowanym rekordzie:

db2 select * from rezerwacje where data =

'2009-09-15' for update with cs

Tak naprawdę dla powyższego zapytania do-danie klauzuli FOR UPDATE nie ma większego sensu. Dopiero wykorzystanie kursorów po-zwoli zademonstrować możliwości tej klau-zuli. Załóżmy, że będziemy chcieli odczy-tać dwa rekordy o datach 2009-09-15 oraz 2009-09-16, a następnie w aplikacji podjąć decyzję o aktualizacji tych rekordów. Do te-go celu wykorzystamy kursor, którym bę-dziemy przemieszczać się po zbiorze wyni-kowym (w naszym przykładzie zbiór będzie składał się z dwóch rekordów). Kursor zade-klarujemy i otworzymy następującymi in-strukcjami SQL:

db2 declare k1 cursor for select * from

rezerwacje where data = '2009-09-

15' or data = '2009-09-16' for

update with cs

db2 open k1

a następnie pobierzemy pierwszy rekord ze zbioru wynikowego:

db2 +c fetch from k1

Pobranie tego rekordu zostało wstrzyma-ne (nie użyliśmy klauzuli ORDER BY, więc w innych warunkach przetwarzanie może być wstrzymane na następnym rekordzie), ponieważ w pierwszej sesji zmodyfikowa-liśmy rekord dla daty 2009-09-15 i nie za-twierdziliśmy zmian. Zatwierdzając trans-akcję w pierwszej sesji, będziemy mogli po-brać rekord:

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-09-15 wtorek 120

Oczekiwanie na wycofanie (bądź zatwier-dzenie) transakcji było związane z tym, że pierwsza sesja utrzymywała blokadę na wyłączność (X) dla rekordu o dacie 2009-09-15. Druga sesja próbowała odczytać re-

Rysunek 1. Brak indeksu na polu data spowoduje wstrzymanie jednej z sesji na blokadzie

Sesja 1update rezerwacje set id_osoby = 33 where data = '2009-01-01

Sesja 2update rezerwacje set id_osoby = 22 where data = '2009-01-05'

2009-01-01 czwartek 332009-01-02 piątek NULL2009-01-03 sobota NULL2009-01-04 niedziela NULL2009-01-05 poniedziałek 222009-01-06 wtorek NULL

Blokada (x)

DB2_EVALUNCOMMITTEDW DB2 istnieje specjalna zmienna rejestrowa DB2_EVALUNCOMMITTED, której włączenie spowoduje, że obie aktualizacje danych z Rysunku 1 powiodą się nawet w przypadku braku indeksu. Zmienną aktywuje się poleceniem db2set:db2set DB2_EVALUNCOMMITTED=YESPo ustawieniu tej zmiennej trzeba jeszcze powtórnie wystartować instancję. Jeśli podczas sekwencyjnego przemieszczania się po tabeli baza napotka na blokadę, wtedy sprawdza aktualną wartość rekordu, by ocenić, czy rekord ten pasuje do kryteriów zapytania. Jeśli re-kord nie pasuje do zbioru wynikowego, wtedy przeszukiwanie jest kontynuowane od na-stępnego rekordu. Osobiście jednak nie zalecamy włączania tej zmiennej, ponieważ wymaga ona odpowiedniego sprofilowania aplikacji. Zmienna ta wykorzystywana jest głównie wte-dy, gdy aplikacja modyfikuje kolumny, które nie są wykorzystywane jako kryterium wyszuki-wania wierszy (tak jak dla przykładu z Rysunku 1: pole data było wykorzystywane do zloka-lizowania rekordów, natomiast uaktualniane było inne pole: id _ osoby). W naszym przeko-naniu dużo lepsze efekty daje odpowiednie zaprojektowanie indeksów oraz normalizacja ta-bel. Projektowanie zdenormalizowanych tabel, które zawierają bardzo dużą liczbę kolumn, powoduje, że aktualizacja dowolnego z pól blokuje także dostęp pozostałych pól w rekor-dzie (blokujemy przecież cały rekord). Rozbicie takiej struktury na wiele węższych tabel spo-woduje, że każda z nich będzie mogła być uaktualniana niezależnie.

Page 53: Sdj Extra 35 Db2

Poziomy izolacji w DB2

53www.sdjournal.org

kord z intencją zapisu, co oznaczało, że chciała założyć blokadę typu UPDATE (U) na tym rekordzie. Blokada (U) nie mo-gła być założona ze względu na wcześniej-szą blokadę (X) z pierwszej sesji. W doku-mentacji DB2 Information Center możesz znaleźć tabelę informującą, jakiego rodza-ju blokady mogą być zakładane na określo-nym rodzaju blokad (szukaj pod hasłem lock compatibility).

Po zatwierdzeniu transakcji w pierwszej sesji (COMMIT) druga sesja odczytała rekord o dacie 2009-09-15, ponieważ odczyt wy-konywany był z intencją zapisu, co ozna-cza, że rekord ten jest automatycznie za-blokowany blokadą typu UPDATE (U). Bloka-da (U) charakteryzuje się tym, że inne sesje mogą czytać rekord (mogą zakładać blokady SHARED (S)), natomiast nie mogą aktualizo-wać rekordu (pośrednio: zakładać blokad tu-pu (X) lub (U)). Innymi słowy, tak długo jak będziemy utrzymywać kursor na danym re-kordzie, nikt inny nie podmieni nam jego za-wartości.

Jeśli przemieścimy się po zbiorze wyniko-wym do następnego rekordu (2009-09-15):

db2 +c fetch from k1

wtedy blokada (U) zostanie zwolniona z pierwszego rekordu (2009-09-15) i zosta-nie założona na kolejnym (2009-09-16). W tym momencie można domyślić się, skąd wzięła się nazwa CURSOR STABI-LITY (stabilność kursora). W tym trybie izolacji baza gwarantuje spójność rekor-du, na którym pozycjonowany jest kur-sor. Podczas przemieszczania się po zbio-rze wynikowym instrukcją FETCH, bloka-dy zakładane są wyłącznie na bieżącym rekordzie (tam, gdzie pozycjonowany jest kursor).

Po sprawdzeniu, czy danego dnia nikt nie dokonywał rezerwacji, rekord możemy uak-tualnić następującą instrukcją SQL:

db2 +c update rezerwacje set id_osoby =

250 where current of k1

Warto zwrócić uwagę na warunek WHE -RE, w którym zamiast klasycznych wa-runków wyszukiwania podaliśmy aktu-alne położenia kursora k1 (CURRENT OF). Aktualizacja z wykorzystaniem bieżącej pozycji kursora (ang. positioned update) jest możliwa wyłącznie w sytuacji, gdy kursor był zadeklarowany z klauzulą FOR UPDATE. W przypadku kursorów tworzo -nych z klauzulą FOR READ ONLY nie jest możliwe wykorzystanie warunku WHERE CURRENT OF, ponieważ dla każdego re-kordu pobranego instrukcją FETCH zakła-dane są blokady typu SHARED (S). Oczy-wiście w przypadku kursorów FOR READ

ONLY możliwa jest aktualizacja danych z wykorzystaniem klasycznego warun-ku wyszukiwania, na przykład poprzez podanie wartości klucza (ang. searched update).

Powyższe instrukcje wykonywaliśmy w oknie poleceń tekstowych DB2 z opcją +c, która wyłącza automatyczny COMMIT. Jeśli kursor nie jest zdefiniowany z klauzulą WITH HOLD, wtedy zatwierdzenie transakcji au-tomatycznie go zamknie, co spowoduje, że przemieszczanie się po zbiorze wynikowym (FETCH) nie będzie możliwe.

Jak wspomnieliśmy na początku, zada-niem poziomów izolacji jest zagwaranto-wanie spójności wykonywanych operacji. Na podstawie odczytanych wartości podej-mujemy decyzję o jego modyfikacji. Gdyby ktoś w międzyczasie podmienił zawartość rekordu, wtedy być może podjęlibyśmy in-ną decyzję – dzięki mechanizmowi blokad taka sytuacja nie będzie miała miejsca. Po-ziom izolacji CS oferuje bardzo dobry sto-pień współbieżności, ponieważ podczas czytania danych w danej chwili utrzymy-wana jest blokada wyłącznie na bieżącym rekordzie. Oczywiście, jeśli zmodyfikuje-my rekord, wtedy blokada (U) zostaje prze-konwertowana do blokady na wyłączność (X) i pozostaje aż do momentu zatwierdze-nia bądź wycofania transakcji. Ten poziom izolacji gwarantuje spójność odczytu i zapi-su, jeśli decydujemy się na modyfikację re-kordu w momencie jego przetwarzania.

Jeśli z góry wiemy, które rekordy chcemy uaktualnić, wtedy nie musimy korzystać z kursorów i możemy wykorzystać pojedynczą instrukcję UPDATE:

update rezerwacje set id_osoby = 250

where data in ('2009-09-15','2009-09-

16') and id_osoby is null

Po wykonaniu instrukcji modyfikującej trzeba jeszcze sprawdzić, które rekordy zo-

stały zmodyfikowane, ponieważ UPDATE zo-stanie wykonany wyłącznie dla dat, dla któ-rych nie zrobiono wcześniej rezerwacji. Za-kładając unikatowość pola data, powyż-sza instrukcja UPDATE może uaktualnić 0,1 lub 2 rekordy. W sytuacji, w której uaktu-alniono tylko część rekordów, aplikacja mu-si podjąć decyzję o wycofaniu zmian (ROL-LBACK albo ROLLBACK to SAVEPOINT) albo za-akceptowaniu rezerwacji na niepełną licz-bę dni.

Do sprawdzenia uaktualnionych rekordów najlepiej wykorzystać instrukcję SELECT opar-tą o instrukcję UPDATE:

select data from new table(

update rezerwacje set id_osoby = 250

where data in ('2009-09-15','2009-09-

16') and id_osoby is null)

który dla naszego zestawu danych zwrócił jeden rekord.

DATA

----------

2009-09-16

Wykorzystanie takiej konstrukcji ma wie-le zalet. Zwracamy tylko te rekordy, któ-re zostały zmodyfikowane. Zapytanie opar-te o instrukcję modyfikującą zapewnia tak-że spójność odczytu, ponieważ obydwie ope-racje wykonywane są w jednym kroku i nie ma „przerwy” pomiędzy modyfikacją a od-czytem.

Rekordy możemy zaktualizować także przy pomocy alternatywnej konstrukcji, w której UPDATE oparty jest o wynik instruk-cji SELECT:

select data from new table(

update (

select data, id_osoby

from rezerwacje

where data in ('2009-09-15','2009-

Rodzaje blokadBlokady mogą być zakładane na rekordach, tabelach, obszarach tabel oraz partycjach tabel. W DB2 dostępne są różne rodzaje blokad. Najbardziej powszechnymi rodzajami blokad za-kładanych na rekordach są:

• Shared (S) – blokada zakładana jest podczas czytania rekordów. Inni użytkownicy mogą czytać rekordy, lecz nie mogą ich modyfikować. Blokady typu (S) mogą być zakładane na blokadach (S) pochodzących od innych użytkowników.

• Update (U) – blokada zakładana jest podczas czytania rekordów z intencją zapisu. In-ni użytkownicy mogą czytać dane, ale nie mogą przystąpić do aktualizacji rekordu, czy-li założyć blokady typu (U) na istniejącej blokadzie (U). Blokady typu (S) mogą być zakła-dane na blokadzie typu (U).

• Exclusive (X) – blokada jest zakładana podczas modyfikacji danych. Właściciel blokady może czytać i uaktualniać dane. Inni użytkownicy nie mogą aktualizować danych. Mogą podejrzeć zawartość rekordu bez sprawdzania blokad: bieżącą wartość w trybie UR bądź wartość sprzed modyfikacji w trybie CS (w wersji DB2 9.7).

Blokady można monitorować poleceniem db2pd –db baza –lock, bądź wykonując zapytania na systemowym widoku SYSIBMADM.SNAPLOCK, który zwraca aktualną listę blokad.

Page 54: Sdj Extra 35 Db2

54

Tworzenie aplikacji

SDJ Extra 35

09-16')

and id_osoby is null

) set id_osoby = 250

)

Wewnętrzne zapytanie wskazuje na listę rekordów, które mają być przekazane do instrukcji aktualizującej. Zewnętrzne za-pytanie zwraca zmodyfikowane rekordy. Obsługa tej instrukcji działa podobnie do omówionego wcześniej przypadku, któ-ry uaktualnia dane na podstawie bieżą-cej pozycji kursora (WHERE CURRENT OF). Wykorzystanie tej konstrukcji daje bar-dzo dużą elastyczność i czasami pozwa-la uniknąć implementacji kursorów przy bardziej złożonych operacjach. Na Listin-gu 2 zademonstrowaliśmy przykład usu-nięcia rekordów o powtarzających się da-tach, zachowując te o najmniejszym ID_OSOBY.

Wykorzystanie tej konstrukcji jest du-żo wydajniejsze i dodatkowo zapewnia du-żo większą współbieżność w porównaniu do implementacji z wykorzystaniem kursorów, ponieważ zadanie wybierz rekordy, usuń je i zwróć usunięte jest realizowane przez bazę da-nych jak jedna operacja SQL.

Przy wykonywaniu instrukcji UPDATE ba-za automatycznie rygluje wyszukane rekor-dy blokadą typu (U) i konwertuje tę bloka-dę do typu (X) w momencie uaktualnienia danych. Oczywiście opisany mechanizm zakładania blokad wykonywany jest auto-matycznie i trwa bardzo krótko. By samo-dzielnie prześledzić ten proces „w zwolnio-nym tempie”, można wykorzystać kursory. Tworzymy kursor oparty o SELECT z klau-zulą FOR UPDATE, przemieszczamy się po zbiorze wynikowym instrukcją FETCH, a na-stępnie aktualizujemy wybrane rekordy z klauzulą WHERE CURRENT OF. W drugiej sesji możemy uruchomić narzędzie db2pd –db sample –lock, by sprawdzić zakładane blo-kady bądź wykorzystać systemowy widok SYSIBMADM.SNAPLOCK.

Znaczenie indeksów dla współbieżnościZnaczenie indeksów omówimy na pro-stym przykładzie. W pierwszej sesji uaktu-alnimy rekord o dacie 2009-01-01, jednak bez zatwierdzania zmian, tak by utrzymać blokadę na wyłączność (X). W drugiej se-sji spróbujemy uaktualnić rekord o datach 2009-01-05, tak jak przedstawione jest to na Rysunku 1.

Obie sesje uaktualniają zupełnie inne ze-stawy rekordów, więc wydaje się, że nie po-winno być konfliktu współbieżności. UPDATE w drugiej sesji zostanie jednak wstrzyma-ny aż do momentu, kiedy pierwsza sesja za-twierdzi bądź wycofa transakcję. Przyczyną takiego zachowania jest brak indeksu na po-

Zakleszczenia (ang. deadlocks)Zakleszczenie (ang. deadlock) powstaje w sytuacji, w której dana sesja oczekuje na zasób, który nigdy nie będzie zwolniony. Najczęstszym przypadkiem zakleszczenia jest sytuacja, w której dwie bądź więcej sesji zakładają blokady typu (S) na tym samym rekordzie, a następ-nie próbują zaktualizować rekord. Aktualizacja rekordu wymaga założenia blokady na wy-łączność (X). Blokada taka nie może być zrealizowana ze względu na blokady (S) pochodzące od innej sesji. Pierwsza sesja zgłaszająca chęć aktualizacji będzie oczekiwać na decyzję dru-giej. Jeśli druga wykona ROLLBACK, wtedy pierwsza bezproblemowo dokończy pracę. Nato-miast jeśli druga z sesji spróbuje także zaktualizować dane, wtedy obie sesje utkwią w mar-twym punkcie: druga będzie czekała na pierwszą, natomiast pierwsza na drugą. DB2 auto-matycznie wykrywa taki przypadek i odkręca transakcje jednej z sesji (ROLLBACK).Do wykrywania zakleszczeń może posłużyć specjalna zmienna rejestrowa:db2set DB2_CAPTURE_LOCKTIMEOUT=ONPo ustawieniu tej zmiennej i zrestartowaniu instancji DB2 będzie gromadziło szczegółowe informacje o zakleszczeniach, w szczególności, jaka sesja była przyczyną zakleszczenia oraz na jakim zasobie nastąpił konflikt.

Listing 3. Zjawisko fantomów występujące w poziomie izolacji RS

Sesja 1: Wykonanie zapytania z poziomem RS

select * from (

select data, dzien, id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

)

with rs;

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-09-10 czwartek -

2009-09-11 piątek -

2009-09-12 sobota -

2009-09-13 niedziela -

2009-09-14 poniedziałek -

Sesja 2: Wstawienie i zatwierdzenie rekordu pasującego do kryterium zapytania z

Sesji 1

insert into rezerwacje values ( '2009-09-11', 'Fantom !', 1);

commit;

DB20000I The SQL command completed successfully.

Sesja 1: Powtórzenie zapytania z poziomem RS

select * from (

select data, dzien, id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

)

with rs;

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-09-10 czwartek -

2009-09-11 piątek -

2009-09-11 Fantom ! 1

2009-09-12 sobota -

2009-09-13 niedziela -

Page 55: Sdj Extra 35 Db2

Poziomy izolacji w DB2

55www.sdjournal.org

lu wykorzystywanym do odszukania zmie-nianego rekordu. By zlokalizować rekord o dacie 2009-01-05, baza danych musi do-konać ewaluacji wartości wszystkich rekor-dów w tabeli (sekwencyjny skan tabeli). Po-nieważ jeden z rekordów (2009-01-01) ma założoną blokadę na wyłączność (X), ewa-luacja wartości tego rekordu będzie możli-wa dopiero po jej zwolnieniu (pamiętajmy, że czytamy z intencją zapisu, więc nie mo-żemy pobrać ostatnio zatwierdzonej war-tości rekordu). Utworzenie indeksu na po-lu data spowoduje, że dla drugiej sesji baza danych uaktualni rekord 2009-01-05 bez konieczności odwoływania się do pozosta-łych rekordów. Wszystkie przykłady z dal-szej części artykułu będziemy wykonywać przy założeniu, że został utworzony nastę-pujący indeks:

db2 create index rez_data_idx on

rezerwacje(data)

READ STABILITYCzasami logika aplikacji wymaga, by decy-zję o sposobie uaktualnienia danych podjąć dopiero po odczytaniu większej części re-kordów. Wyobraźmy sobie, że chcemy doko-nać rezerwacji na 5 dni w całości, licząc od 2009-09-10.

Najpierw musimy sprawdzić, czy w tych dniach nikt inny nie dokonał rezerwacji. Ta-kie sprawdzenie możemy wykonać następują-cym zapytaniem SQL:

select count(id_osoby) from (

select id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

)

Wewnętrzne zapytanie wybiera listę pię-ciu rekordów dla dni, które nas interesują. Zewnętrzne zapytanie zlicza id _ osoby. Zewnętrzne zapytanie zwróci wartość ze-ro, jeśli w polu ID _ OSOBY znajdowały się wyłącznie wartości NULL, co oznacza, że w tych dniach nie dokonano rezerwa-cji. Jeśli zapytanie zwróci wartość więk-szą od zera, wtedy musimy poszukać in-nego terminu albo zmienić liczbę rezerwo-wanych dni.

Dla naszych danych zapytanie zwróciło wartość zero, więc możemy przystąpić do rezerwacji, uaktualniając kolumnę id_osoby dla pięciu rekordów:

update (

select id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

) set id_osoby = 123

Taki sposób rezerwacji byłby poprawny przy założeniu, że jesteśmy jedyną osobą, która jest podłączona do bazy danych. Rezerwa-cji dokonujemy w dwóch krokach. W pierw-szym czytamy dane z bazy, a w drugim zapi-sujemy. Pomiędzy wykonaniem tych dwóch operacji jest przerwa, w której inna aplika-

cja może zaktualizować dane. Gdyby tak się stało, wtedy wykonana przez nas instrukcja UPDATE doprowadzi do niespójności w bazie, ponieważ w ten sposób nadpiszemy rezerwa-cję wykonaną przez innego użytkownika.

Rozwiązaniem problemu może być zablo-kowanie rekordów już w momencie odczy-

Listing 4. Lista blokad przy poziomie izolacji RR zależnie od istnienia indeksu na polu data. Listę uzyskaliśmy, wykonując zapytanie na widoku systemowym SYSIBMADM.SNAPLOCK

Lista blokad dla zapytania z Listingu 3 wykonanego z poziomem RR po usunięciu

indeksu na polu data

LOCK_OBJECT_TYPE TABNAME LOCK_MODE LOCK_ATTRIBUTES

------------------ ---------- ---------- ---------------

INTERNALP_LOCK – S NONE

TABLE_LOCK REZERWACJE S RR

Lista blokad dla zapytania z Listingu 3 z poziomem RR przy istniejącym indeksie na

polu data

LOCK_OBJECT_TYPE TABNAME LOCK_MODE LOCK_ATTRIBUTES

------------------ ---------- ---------- ---------------

ROW_LOCK REZERWACJE S RR

ROW_LOCK REZERWACJE S RR

ROW_LOCK REZERWACJE S RR

ROW_LOCK REZERWACJE S RR

ROW_LOCK REZERWACJE S RR

INTERNALP_LOCK – S NONE

TABLE_LOCK REZERWACJE IS RR

Rysunek 2. Modyfikacja rekordów bez wstępnego blokowania przy odczycie

2009-01-10 czwartek NULL2009-01-11 piątek NULL2009-01-12 sobota NULL2009-01-13 niedziela NULL2009-01-14 poniedziałek NULL2009-01-15 wtorek NULL

2009-01-12 sobota 1502009-01-13 niedziela 150

...

...

I. Odczyt bez blokowaniaIII. Zlokalizowanie rekordów i spójny zapis

II. Aktualizacja po stronie aplikacji

Baza danych

Aplikacja

Page 56: Sdj Extra 35 Db2

56

Tworzenie aplikacji

SDJ Extra 35

tu, tak by inni użytkownicy nie mogli zmo-dyfikować w międzyczasie wybranych przez nas rekordów. Taką funkcjonalność oferu-je właśnie poziom izolacji READ STABILITY (RS). W tym trybie izolacji każdy odczyta-ny rekord, który pasuje do zbioru wynikowe-go, jest domyślnie blokowany blokadą typu SHARED (S), co oznacza, że inni użytkowni-cy nie będą mogli założyć blokady typu (X) na tych rekordach. Zapytanie wykorzystują-ce poziom izolacji RS będzie wyglądało na-stępująco:

select count(id_osoby) from (

select id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

)

with rs

Po wykonaniu tego zapytania wszystkie odczytane rekordy zostaną zablokowane blokadą (S). Blokada typu (S) dopuszcza zakładanie przez innych użytkowników na tym samym rekordzie dodatkowych blokad typu (S). Inny użytkownik będzie mógł wykonać dokładnie to samo zapyta-nie sprawdzające i także dostanie taki sam wynik.

W takiej sytuacji dwie sesje będą prze-konane, że nikt wcześniej jeszcze nie doko-nał rezerwacji w tych terminach, więc oby-dwie sesje mogą przystąpić do wykonania in-strukcji UPDATE. Pierwsza z sesji, która wyko-na UPDATE, zostanie wstrzymana, ponieważ nie będzie mogła założyć blokad (X) na re-kordach, które posiadają blokady typu (S) pochodzące od drugiej sesji. Jeśli druga se-sja wykona UPDATE, wtedy także zostanie wstrzymana z tego samego powodu – na zmienianych rekordach występują blokady

typu (S) założone z pierwszej sesji. W tym momencie DB2 wykrywa, że wystąpiło za-kleszczenie (ang. deadlock) i wycofuje jed-ną z transakcji. Jedna z transakcji kończy się z błędem:

SQL0911N The current transaction has been

rolled back because of a deadlock

or timeout. Reason code "68".

SQLSTATE=40001

Przedstawione rozwiązanie zabezpiecza przed niespójnością danych, jednak jest mało eleganckie ze względu na stosunkowo wysoki koszt obsługi zakleszczenia. Przy takim podejściu decyzja o wstrzymaniu przetwarzania którejś z sesji zostanie odło-żona aż do momentu wykonania instruk-cji UPDATE.

Gdyby wszystkie aplikacje wykorzystywa-ły do sprawdzania zapytanie wykorzystujące blokady typu (U), tak jak poniżej:

select count(id_osoby) from (

select id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

)

with rs use and keep update locks

, wtedy druga sesja zostanie wstrzymana już w momencie wykonywania zapytania spraw-dzającego aż do czasu skończenia transakcji przez pierwszą sesję. Takie zachowanie wy-nika stąd, że DB2 nie dopuszcza zakładania blokady (U) na rekordzie, który już blokadę (U) posiada.

Blokada typu (U) zezwala jednak na zakła-danie blokad typu (S) przez inne sesje. W sy-tuacji, w której aplikacje niekonsekwentnie wykorzystują rodzaje blokad (jedne wykorzy-

stują blokadę typu (U), inne typu (S)), może dojść do zakleszczenia. By zapobiec takiej sy-tuacji, możemy zastosować blokadę na wy-łączność (X):

select count(id_osoby) from (

select id_osoby from rezerwacje

where data >= '2009-09-10'

order by data asc

fetch first 5 rows only

)

with rs use and keep exclusive locks

Takie rozwiązanie zagwarantuje, że żadna inna sesja nie podejrzy przetwarzanych da-nych, jeśli chce przeczytać rekordy z zamia-rem ich modyfikacji. Inne sesje chcące doko-nać sprawdzenia zawartości mogą odczytać dane z wykorzystaniem poziomu CS Cur-rently Committed (DB2 9.7), który pozwoli na odczytanie zatwierdzonych danych z po-minięciem blokad.

REPEATABLE READPoziom REPEATABLE READ (RR) działa bar-dzo podobnie do poziomu RS. Każdy z odczytanych rekordów jest blokowany aż do momentu zatwierdzenia bądź wycofa-nia transakcji.W przypadku wykorzysta-nia kursorów istnieje możliwość wcześniej-szego zwolnienia blokad, zamykając kursor instrukcją CLOSE kursor WITH RELEASE. Do-myślnie utrzymywane są blokady typu (S), a przy wykorzystaniu klauzuli USE AND KEEP możliwe jest także użycie blokad typu (U) lub (X). Jedyną różnicą pomiędzy pozio-mem RR w porównaniu do RS jest to, że baza danych nie zezwala, by inna sesja wsta-wiła rekord, który będzie pasował do zbio-ru wynikowego zapytania. Odnosząc się do wcześniejszego przykładu wykorzystujące-go poziom RS, przy wielokrotnym wykony-waniu tego samego zapytania istnieje nieze-rowe prawdopodobieństwo uzyskania róż-nych wyników. Tak jak pokazaliśmy na Li-stingu 3, rekord wstawiony w Sesji 2 jest widoczny w Sesji 1 po powtórnym wykona-niu tego samego zapytania. Dodatkowy re-kord, który pojawił się w Sesji 2, określany jest jako „Fantom”.

Poziom izolacji REPEATABLE READ z defini-cji nie dopuszcza Fantomów. W przypadku poziomu izolacji RR baza danych gwarantuje, że wielokrotne wykonywanie tego samego za-pytania zawsze zwróci dokładnie ten sam ze-staw rekordów. Gdyby zapytanie z Listingu 3 wykorzystywało poziom RR, wtedy instruk-cja INSERT w Sesji 2 zostałaby wstrzymana aż do momentu zatwierdzenia bądź wycofa-nia transakcji.

REPEATABLE READ jest najbardziej re-strykcyjnym poziomem izolacji dostęp-nym w DB2. Dla tego poziomu bardzo du-że znaczenie ma zaprojektowanie odpo-

Listing 5. Pobranie adresu rekordu i tokenu modyfikacji rekordu

db2 select id_osoby, rid_bit(rezerwacje), row change token for rezerwacje from

rezerwacje where data = '2009-09-10'

ID_OSOBY 2 3

----------- ----------------------------------- --------------------

x'14008200000000000000000002795824' 3999328423385235456

Listing 6. Pobranie znacznika czasu modyfikacji rekordu

db2 select data, dzien, id_osoby, row change timestamp for rezerwacje from

rezerwacje

DATA DZIEN ID_OSOBY 4

---------- --------- ----------- --------------------------

2009-01-01 czwartek – 0001-01-01-00.00.00.000000

2009-01-02 piątek – 0001-01-01-00.00.00.000000

2009-01-03 sobota – 0001-01-01-00.00.00.000000

Page 57: Sdj Extra 35 Db2

Poziomy izolacji w DB2

57www.sdjournal.org

wiednich indeksów, tak by blokować tyl-ko te instrukcje INSERT, które faktycznie mogą spowodować Fantomy. Gdyby zapy-tanie w Sesji 1 z Listingu 3 było wykona-ne z poziomem izolacji RR, a indeks na po-lu data zostałby wcześniej usunięty, wte-dy zapytanie wymusiłoby założenie jed-nej blokady (S) na całej tabeli. Taka bloka-da gwarantuje, że nikt nie wstawi rekordu pasującego do zbioru wynikowego zapyta-nia, jednak jednocześnie ogranicza współ-bieżność. Wszystkie pozostałe sesje nie bę-dą mogły modyfikować danych tabeli re-zerwacje aż do czasu zakończenia transak-cji w Sesji 1.

Założenie indeksu na polu data pozwoli bazie danych zakładać blokady dla każdego z rekordów. Dzięki takiej granulacji blokad druga sesja może pomyślnie wstawić rekord o dacie 2009-10-01. Na Listingu 4 przedsta-wiliśmy listę blokad, które są zakładane przez zapytanie z Listingu 3 dla obu omówionych przypadków.

Podsumowanie poziomów CS, RR i RSPoziomy izolacji RS i RR utrzymują bloka-dy aż do momentu zatwierdzenia transak-cji. By zapewnić maksymalną współbież-ność, a tym samym wydajność przetwa-rzania, przerwa pomiędzy odczytaniem danych a zatwierdzeniem powinna być możliwie krótka. Organizacja aplikacji, w której najpierw prezentujemy użytkowni-kowi na ekranie zestaw rekordów odczy-tanych w trybie RR lub RS, a następnie czekamy na decyzję użytkownika, nie za-wsze jest najlepszym rozwiązaniem. Przy takim podejściu dane byłyby blokowane tak długo, jak długo użytkownik przeglą-dałby je na ekranie. Dużo lepszym podej-ściem jest zaprezentowanie użytkowniko-wi danych na ekranie bez blokowania, a kiedy już wstępnie podejmie decyzję, wte-dy powinniśmy wykorzystać odpowiedni poziom izolacji, by w spójny sposób uak-tualnić bazę danych. „Spójne uaktualnie-nie” oznacza, że zaktualizujemy właściwe rekordy w zamierzony sposób. Jeśli ktoś w międzyczasie dokona modyfikacji inte-resujących nas rekordów, wtedy proces za-pisu powinien to wykryć. Schemat takie-go uaktualnienia został przedstawiony na Rysunku 2.

Odnosząc się do wcześniej opisywanego przykładu, rezerwację na 5 kolejnych dni mo-żemy zrealizować na kilka sposobów z wyko-rzystaniem dowolnego z omówionych pozio-mów izolacji:

• od razu aktualizujemy dane i sprawdza-my spójność aktualizacji;

• wykorzystujemy kursor działający i ak-tualizujemy rekord po rekordzie.

Najpierw blokujemy rekordy, a potem je ak-tualizujemy.

W Podejściu 1 od razu wykonujemy in-strukcję UPDATE na interesującym nas zesta-wie rekordów bez zatwierdzania transakcji i w tym samym kroku odczytujemy zaktu-alizowane rekordy sprzed aktualizacji. Do tego celu możemy wykorzystać konstruk-cję SELECT FROM NEW / OLD TABLE opartą o instrukcję modyfikującą UPDATE, INSERT, DELETE. Gdyby w wyniku sprawdzenia oka-zało się, że nasza aktualizacja jest niespój-na, wtedy należałoby wycofać zmiany in-strukcją ROLLBACK, najlepiej wykorzystu-jąc punkt zapisu transakcji (ROLLBACK TO SAVEPOINT). Takie podejście może być sto-sowane wtedy, gdy logika weryfikacji spój-ności zapisu jest stosunkowo prosta. W na-szym przykładzie wystarczyło sprawdzić pole ID_OSOBY. W ogólnym przypadku sprawdzenie może wymagać porównania większej liczby pól. Wadą tego rozwiąza-nia może być także konieczność wycofania zmian w przypadku wystąpienia niespój-nego zapisu. Instrukcja ROLLBACK jest sto-sunkowo kosztowną operacją, dlatego jeśli zakładamy, że takie sytuacje mogą wystę-pować względnie często, wtedy lepiej jest wcześniej zablokować rekordy, tak jak w Podejściu 3.

W Podejściu 2 wykorzystujemy kursor działający w poziomie izolacji CS i aktuali-zujemy rekord po rekordzie. Gdyby okaza-ło się, że któryś z rekordów jest już zaktu-alizowany przez innego użytkownika (np. rezerwacja na jeden dzień z interesujących nas 5 dni), wtedy wycofujemy zmiany. Po-dejście z wykorzystaniem kursorów pozwa-la na implementację bardziej złożonej logi-ki. Obsługa kursorów jest jednak bardziej kosztowna niż wykonanie pojedynczej in-strukcji SELECT z UPDATE, głównie ze wzglę-du na konieczność wykonania bardzo du-żej ilości instrukcji SQL FETCH. Po kursory warto sięgnąć wtedy, gdy logika przetwarza-nia jest trudna do uzyskania pojedynczą in-strukcją SQL.

W Podejściu 3 najpierw blokujemy inte-resujące nas dane, wykorzystując poziom izolacji RR / RS (najlepiej korzystając z klauzuli USE AND KEEP UPDATE | EXCLUSIVE LOCKS, by wyeliminować zakleszczenia), a następnie zatwierdzamy zmiany. Gdy-by okazało się, że nie jesteśmy w stanie za-blokować interesującego zestawu rekordów (ktoś dokonał w międzyczasie modyfika-cji), wtedy po prostu zwalniamy blokady. Zaletą podejścia 3 jest to, że jeśli już uzy-skamy blokady (X) na odczytanych rekor-dach, to mamy gwarancję pomyślnego za-twierdzenia transakcji. Ewentualny kon-flikt dostępu do danych zostanie wykry-ty już we wczesnej fazie blokowania re-kordów przy odczycie, co pozwala unik-nąć wycofania transakcji bądź wystąpienia zakleszczeń.

Optymistyczne blokowanieAktualizacja rekordów zgodnie ze sche-matem pokazanym na Rysunku 2 wymaga zlokalizowania właściwych rekordów oraz upewnienia się, czy rekordy te nie zosta-ły w międzyczasie uaktualnione przez ko-goś innego. Taka czynność może okazać się nietrywialna, jeśli tabela składa się z dużej liczby pól. Tu z pomocą przychodzą specjal-ne funkcje do obsługi optymistycznego blo-kowania dostępne od wersji DB2 9.5, które pozwalają szybko zlokalizować dany rekord oraz sprawdzić, czy był zmodyfikowany od ostatniego odczytu.

Do zlokalizowania odczytanego rekordu można wykorzystać funkcję RID_BIT (ang. row identifier), która zwraca adres rekordu. Do sprawdzenia, czy rekord był modyfiko-wany od ostatniego odczytu, może posłużyć wyrażenie ROW CHANGE TOKEN. Odczytując da-ne, powinniśmy dodatkowo pobrać adres re-kordu oraz token ostatniej modyfikacji (patrz Listing 5).

Funkcja RID_BIT zwraca identyfikator re-kordu w postaci ciągu znaków VARCHAR(16) FOR BIT DATA. Klauzula FOR BIT DATA powodu-je, że ciąg znaków traktowany jest jak zestaw

Listing 7. Automatyczna aktualizacja pola CZAS_AKTUALIZACJI zdefiniowanegojako ROW CHANGE TIMESTAMP

db2 update rezerwacje set id_osoby = id_osoby

db2 commit

db2 select data, dzien, id_osoby,czas_aktualizacji from rezerwacje

DATA DZIEN ID_OSOBY CZAS_AKTUALIZACJI

---------- --------- ----------- --------------------------

2009-01-01 czwartek – 2009-06-12-12.15.41.343000

2009-01-02 piątek – 2009-06-12-12.15.41.343001

2009-01-03 sobota – 2009-06-12-12.15.41.343002

Page 58: Sdj Extra 35 Db2

58

Tworzenie aplikacji

SDJ Extra 35

danych binarnych i nie jest poddawany kon-wersji stron kodowych. Okno poleceń DB2 prezentuje dane tego typu heksadecymalnie ( x'1400…824'). ROW CHANGE TOKEN zwraca war-tość typu BIGINT.

Aktualizując wybrany rekord w warunku WHERE, podajemy poprzednio pobrany adres rekordu oraz token modyfikacji:

db2 update rezerwacje set od_osoby = 300

where rid_bit(rezerwacje) = x'140

08200000000000000000002795824' and

row change token for rezerwacje =

3999328423385235456

Jeśli okaże się, że w międzyczasie ktoś zmienił wartość rekordu, wtedy automa-tycznie zmieni się token modyfikacji i ak-tualizacja nie zostanie wykonana, ponie-waż aktualizowany rekord nie zostanie od-naleziony. Instrukcja UPDATE zakończy się ostrzeżeniem:

SQL0100W No row was found for FETCH,

UPDATE or DELETE; or the result

of a query is an empty table.

SQLSTATE=02000

Korzystanie z identyfikatora rekordu oraz tokenu modyfikacji nie wymaga specjal-nego konfigurowania tabeli. Identyfika-tor rekordu jest po prostu adresem logicz-nym rekordu. Token modyfikacji jest spe-cjalnym znacznikiem umieszczanym na stronie, na której znajdują się rekordy, i jest zmieniany za każdym razem, kiedy ba-za aktualizuje stronę. Łatwo się domyślić, że przemieszczenie rekordu z jednej stro-ny na inną (na przykład podczas reorgani-zacji tabeli poleceniem REOGR) spowodu-je zmianę adresu oraz zmianę tokenu mo-dyfikacji. Token modyfikacji ROW CHANGE TOKEN tak naprawdę zmieniany jest za każ-dym razem, kiedy jakikolwiek z rekordów na stronie zostanie zaktualizowany. Wraz ze wzrostem liczby modyfikacji na tabe-li wzrasta prawdopodobieństwo, że apli-kacja wykorzystująca token nie odnajdzie wcześniej odczytanego rekordu, mimo iż rekord nie był zmodyfikowany (ang. fal-se positives).

By zabezpieczyć się przed takimi przy-padkami, możliwe jest dodanie dodatko-wej kolumny ROW CHANGE TIMESTAMP, któ-ra będzie przechowywała unikatowy znacz-nik czasowy (ang. timestamp) aktualizacji każdego rekordu. ROW CHANGE TIMESTAMP jest pewnego rodzaju licznikiem, którego wartość oparta jest o bieżący czas. Znacz-nik w przybliżeniu odpowiada aktualne-mu czasowi modyfikacji rekordu (CURRENT TIMESTAMP). Określenie „przybliżony” nale-ży rozumieć w ten sposób, że znacznik mo-że chwilowo wyprzedzić bieżący czas w sy-

tuacjach, w których rozdzielczość znaczni-ka jest mniejsza niż liczba modyfikacji w da-nym przedziale czasowym (więcej niż 1 mi-lion modyfikacji na sekundę). Takie zacho-wanie znacznika pozwala zagwarantować je-go unikatowość.

Znacznik czasowy ROW CHANGE TIMESTAMP najlepiej dodać w postaci ukrytej kolumny (ang. hidden column), tak by dodanie kolum-ny nie wymuszało modyfikacji istniejących aplikacji. Dla naszej tabeli rezerwacje znacz-nik czasowy dodamy, wykorzystując instruk-cję ALTER TABLE:

alter table rezerwacje add column czas_

aktualizacji timestamp not null

implicitly hidden generated always

for each row on update as row

change timestamp;

Po dodaniu kolumny tabela będzie zachowy-wała się dokładnie tak samo jak przed jej do-daniem. Zapytania nie będą świadome ist-nienia kolumny czas_aktualizacji:

db2 select * from rezerwacje

DATA DZIEN ID_OSOBY

---------- ------------- -----------

2009-01-01 czwartek -

2009-01-02 piątek -

2009-01-03 sobota -

By sprawdzić zawartość kolumny czas_ak-tualizacji, musimy jawnie podać jej nazwę bądź wykorzystać wyrażenie ROW CHANGE TIMESTAMP (patrz Listing 6)

Jak widać, po wykonaniu instrukcji ALTER TABLE kolumna czas_aktualizacji zosta-ła wypełniona stałą wartością 0001-01-01-00.00.00.000000. Wystarczy zaktualizować dowolne pole tabeli, by przekonać się, że ko-lumna czas_aktualizacji jest także zmieniana (patrz Listing 7).

Aktualizacja rekordu z wykorzysta-niem znacznika czasowego wygląda na-stępująco:

update rezerwacje set id_osoby = 150

where

(rid_bit(rezerwacje) = x'530041080000000

000000000043A47BA' and

row change timestamp for rezerwacje =

'2009-06-12-12.15.41.343222');

Jeśli w transakcji będziemy chcieli uaktual-nić większą liczbę rekordów, wtedy powyż-szą instrukcję UPDATE musimy wykonać wie-le razy, za każdym razem sprawdzając licz-bę zaktualizowanych wierszy. W przypad-ku gdyby instrukcja UPDATE nie odszukała rekordu o zadanym identyfikatorze i znacz-niku czasowym, wtedy aplikacja powinna obsłużyć taką sytuację (np. poinformować o

podmianie danych i zaczytać rekordy jesz-cze raz).

PodsumowanieKluczem do stworzenia maksymalnie współbieżnej aplikacji jest odpowied-nie zaprojektowanie tabel oraz indeksów. Równie istotnym elementem jest wyko-rzystanie odpowiednich poziomów izo-lacji, by zagwarantować spójność wyko-nywanych operacji oraz maksymalną wy-dajność. DB2 udostępnia techniki zarów-no optymistycznego, jak i pesymistyczne-go blokowania danych. W optymistycz-nym blokowaniu przyjmuje się założe-nie, że odczytane przez aplikację rekordy nie będą w międzyczasie zmodyfikowane przez inną aplikację, więc nie ma potrze-by zakładania blokad. Takie rozwiązanie gwarantuje maksymalną współbieżność. Gdyby okazało się, że raz na jakiś czas in-na aplikacja zmodyfikuje odczytane przez nas dane, wtedy musimy powtórzyć wyko-naną przez nas pracę. W przypadku pesy-mistycznego blokowania przyjmujemy za-łożenie, że istnieje bardzo duża szansa, że odczytane dane za chwilę będą modyfiko-wane przez inną osobę. W takim przypad-ku opłaca się już na samym początku za-blokować dane, by mieć gwarancję wyko-nania założonych modyfikacji. Przyjęcie odpowiednich założeń musi być oparte o przewidywaną charakterystykę aplika-cji oraz sposób, w jaki użytkownicy będą z niej korzystać.

ARTUR WROńSKIJest pracownikiem działu oprogramowania IBM (IBM Software Group) i specjalizuje się w zagad-nieniach baz danych DB2 oraz Informix.Kontakt z autorem: [email protected]

KRZYSZTOf MIKOłAjEWSKIKrzysztof Mikołajewski pracuje w dziale usług technologicznych IBM Software Services. Zajmu-je się zagadnieniami baz danych DB2, Informix oraz narzędziami do integracji danych, stosowa-nymi w hurtowniach danych.Kontakt z autorem: [email protected]

Page 59: Sdj Extra 35 Db2
Page 60: Sdj Extra 35 Db2

60

Administracja

SDJ Extra 35

Z anim jednak skoncentruję się na odpo-wiednich narzędziach i przedstawię przykłady ich użycia, chciałbym pod-

kreślić, iż żadna technika zabezpieczania danych nie zastąpi kopii bezpieczeństwa wykonywanych z poziomu bazy danych. Zapis lustrzany na nie-zależne dyski (ang. mirroring), replikacja bazy da-nych na wiele ośrodków, wyładowanie danych z bazy do postaci tekstowej czy kopia plików bazy danych wykonanych z poziomu systemu opera-cyjnego mogą być jedynie uzupełnieniem bac-kup-ów, a nie podstawowym elementem zabez-pieczania danych. Mój punkt widzenia przybliżę w następnych sekcjach artykułu. Wszystkie pole-cenia zawarte w tym artykule oparłem na bazie przykładów SAMPLE, którą można utworzyć, wy-dając polecenie: db2sampl –sql.

Pierwszy backupNajprostszą formą backup-u jest tzw. archiwum offline. Ten rodzaj kopii bezpieczeństwa wykonu-je się przy nieaktywnej bazie danych. Wystarczy, że w oknie poleceń tekstowych DB2 wpiszesz:

db2 backup db sample

Jeśli baza danych była wcześniej aktywo-wana przez administratora poleceniem db2 activate db sample bądź pośrednio poprzez podłączenie się użytkowników do bazy, wte-dy dostaniesz komunikat o błędzie SQL1035N The database is currently in use. W ta-kiej sytuacji musisz odłączyć użytkowników od bazy, deaktywować bazę danych (jeśli by-ła wcześniej jawnie aktywowana poleceniem activate database) i dopiero wtedy mo-żesz wykonać polecenie tworzące kopię bez-pieczeństwa. Na Listingu 1 załączyłem przy-kładowy skrypt, który wykonuje te operacje. Polecenie quiesce przeprowadza bazę w tryb administracyjny z jednoczesnym i natych-miastowym odłączeniem użytkowników od bazy. Standardowo baza danych jest bowiem aktywowana, tzn. startowane są zasoby (pa-

mięć, odpowiednie wątki obsługujące żąda-nia itp.) automatycznie podczas pierwszego podłączenia się któregokolwiek z użytkowni-ków. Jeśli baza danych jest w trybie quiesce (określanym także jako tzw. tryb wygaszonej bazy danych), wtedy wyłącznie administra-torzy bazy, posiadający odpowiedni poziom uprawnień quiesce, będą mogli się do danej bazy danych podłączyć. Innymi słowy wyko-nanie polecenia quiesce gwarantuje, że ża-den zwykły użytkownik bazy danych nie bę-dzie mógł aktywować bazy danych, więc bac-kup offline ma szansę się wykonać.

Wykonanie w skrypcie polecenia deactivate db sample jest konieczne wyłącznie wtedy, jeśli baza danych była wcześniej aktywowana polece-niem activate db sample. W praktyce stosun-kowo rzadko aktywuje i deaktywuje się bazę da-nych w jawny sposób. Jeśli baza była aktywowa-na poprzez podłączenie się do niej, wtedy ostat-ni użytkownik, który odłącza się od bazy, inicju-je proces deaktywacji bazy. Takie podejście po-zwala na efektywniejsze wykorzystanie pamię-ci komputera w przypadku wielu baz, ponie-waż bazy, z których nikt nie korzysta, nie kon-sumują zasobów. Jeśli baza była jednak aktywo-wana jawnie, wtedy nawet odłączenie wszyst-kich użytkowników od bazy (np. poleceniem quiesce, tak jak w skrypcie na Listingu 1) nie inicjuje procesu zwalniania zasobów.

Istnieje jeszcze kilka metod odłączania użyt-kowników od bazy. Użytkowników możesz odłączyć także poleceniem force application, podając odpowiednie numery sesji, np.: force application (69,77,100). Wtedy jednak nie masz gwarancji, że pomiędzy wykonaniem tej operacji a rozpoczęciem backup-u offline ktoś nie podłączy się do bazy, co spowoduje, że wykona-nie backup-u offline nie powiedzie się. Inną, bar-dzo destruktywną metodą zatrzymywania zaso-bów bazy jest na przykład zabicie z poziomu sys-temu operacyjnego głównego procesu menadże-ra DB2 (db2syscs.exe w systemach Windows lub db2sysc w systemach Linux/UNIX) lub po pro-

stu wyłączenie zasilania w komputerze. Oczywi-ście takiej operacji wykonywać na bazie danych nie wolno, ponieważ baza nie zostanie popraw-nie zamknięta i pozostanie w niespójnym stanie! Wówczas próba wykonania backup-u offline bazy skutkuje wyświetleniem komunikatu o błędzie SQL1015N The database is in an inconsistent state. Stan niespójności oznacza, że baza danych na dyskach zawiera dane pochodzące z transak-cji, które nie zostały zatwierdzone. By o tym się przekonać, wystarczy wyświetlić konfigurację bazy (get db cfg for sample) i poszukać parame-tru informacyjnego „Database is consistent”. War-tość „NO” oznacza, że baza jest niespójna i dla ta-kiej bazy backup offline nie może być wykonany. Co ciekawe, parametr ten przyjmuje także war-tość „NO” podczas normalnej pracy, gdy użyt-kownicy modyfikują dane. Wcale nie oznacza to, że z bazą są jakieś problemy, a jedynie tylko tyle, że są rozpoczęte transakcje, a stan plików bazy na dyskach może nie odzwierciedlać stanu operacji wykonanych w pamięci bazy. Wszystkie mody-fikacje danych w DB2 są wykonane w pamięci operacyjnej (w tzw. puli buforów) i okresowo są przenoszone na dysk. Zapis do plików bazy (tzw. kontenerów) odbywa się zupełnie asynchronicz-nie i jest odpowiednio kolejkowany, by zoptyma-lizować dostęp do systemu dyskowego.

Możesz być zaskoczony, ale skrypt przedsta-wiony na Listingu 1 powinien poprawnie wy-konać backup offline nawet dla takiej bazy, która była niepoprawnie zamknięta. Dlaczego? Dlate-go że w pierwszej linii skryptu łączymy się do ba-zy danych, a taka operacja domyślnie inicjuje pro-ces automatycznego uspójniania bazy po niepo-prawnym zamknięciu. Taki proces określany jest jako crash recovery. Na podstawie informacji z pli-ków dziennika transakcji (tzw. logów transakcyj-nych) wycofywane są niezatwierdzone transak-cje (ang. rollback) oraz nanoszone są na dysk te transakcje, które były już zatwierdzone (ang. rol-lforward). Jak już wcześniej wspomniałem, baza nie gwarantuje, że wykonane w pamięci zmia-ny zostaną od razu naniesione na dysk do odpo-

Backup od A do Z

Na administratorze bazy danych spoczywa wiele obowiązków. Jednym z nich, w moim przekonaniu najważniejszym, jest odpowiednie zabezpieczenie danych poprzez wykonywanie kopii zapasowych, czyli tzw. backup-ów. W ramach artykułu przybliżę dostępne w DB2 techniki ich tworzenia.

Artur Wroński

Page 61: Sdj Extra 35 Db2

Backup od A do Z

61www.sdjournal.org

wiednich kontenerów, natomiast gwarantuje, że każda zatwierdzona transakcja jest zapisana na dysk do plików dziennika transakcji. Jak wi-dać, dziennik transakcji jest bardzo ważnym ele-mentem bazy danych. Awaria systemu, która po-wiązana jest z uszkodzeniem bądź utratą plików dziennika transakcji, zwykle wiąże się z koniecz-nością odtworzenia bazy z backup-u.

Rezultatem pomyślnego zakończenia polece-nia db2 backup db sample jest utworzenie odpo-wiedniego pliku, który jest binarnym obrazem spójnej bazy danych. Nazwa pliku archiwum przyjmuje następującą postać:

SAMPLE.0.DB2.NODE0000.CATN0000.2009050322

4103.001

Domyślnie plik backup-u tworzony jest w ka-talogu, w którym zostało wykonane polecenie backup-u. Bardzo dobrą praktyką jest podanie w poleceniu backup database ścieżki, gdzie ma być umieszczony obraz archiwum, ponie-waż pełna treść poleceń backup database zo-staje dodatkowo zapisywana do pliku z histo-rią operacji administracyjnych bazy (mamy wówczas informacje o lokalizacji naszego bac-kup-u). W pliku backup-u zostaną zachowa-ne strony z danymi oraz indeksami. Co waż-ne, zapisywane są tylko te, które zawierają da-ne – strony puste nie są kopiowane, stąd ob-raz archiwum zwykle zajmuje mniej niż pli-ki bazy na dyskach. Jeśli dodatkowo włączy-my opcję kompresji backup-u (dostępna jest w cenie wszystkich edycji DB2), wtedy tworzony obraz powinien być kilkukrotnie mniejszy (po-lecenie: backup db sample compress).

W obrazie archiwum umieszczona jest także konfiguracja bazy, informacje o kontenerach, a także plik z historią operacji administracyjnych. W nazwie pliku archiwum zakodowany jest do-kładny czas wykonania archiwum, czyli tzw. znacznik czasowy (ang. timestamp). Dla podane-go przykładu 20090503224103 oznacza 3 maja 2009, 22-gą godzinę, 41-szą minutę oraz 3-cią sekundę. Nazwa obrazu backup-u zawiera tak-że informację o nazwie bazy danych, dla której wykonywana była kopia bezpieczeństwa (SAM-PLE) oraz nazwę instancji (DB2). By odtworzyć bazę danych z backup-u, wystarczy wpisać w oknie poleceń tekstowych:

db2 restore db sample

Narzędzie odtwarzające bazę danych będzie oczekiwało, że w katalogu, z którego było wy-konywane polecenie, znajdować się będzie ob-raz archiwum, a nazwa pliku obrazu będzie zgodna z postacią przedstawioną wcześniej. Oczywiście możesz bezpośrednio w skład-ni polecenia restore podać ścieżkę, z której chcesz wczytać backup. Jeśli w danej ścieżce istnieje więcej niż jeden obraz archiwum, wte-dy musisz wskazać, który obraz archiwum ma być odtworzony. Wybranie konkretnego ob-

razu archiwum określa się poprzez podanie znacznika czasowego. Poniższy przykład od-twarza bazę danych z katalogu c:\temp, z bac-kup-u wykonanego dnia 2009.05.04 o godzi-nie 01.32.19 (godzina.minuta.sekunda):

db2 restore db sample from "c:\temp" taken

at 20090504013219

W poleceniu restore możesz podać skróco-ny punkt w czasie, np. taken at 20090504. Ważne jest, by podany znacznik czasowy jednoznacznie identyfikował obraz archi-wum. Jeśli w dniu 2009.05.04 był wykony-wany tylko jeden backup, wtedy wystarczy podać 20090504. Jeśli danego dnia backup wykonywany był wiele razy, należy doprecy-zować godzinę, np. 2009050401.

Jeśli nie jesteś pewny co do nazwy obrazu ar-chiwum, zawsze możesz sprawdzić szczegóły ob-razu narzędziem db2ckbkp (db2 check backup). Narzędzie uruchomione z opcją –H podaje wie-le cennych informacji, takich jak znacznik cza-sowy zakończenia backup-u, poprawna oczeki-wana nazwa pliku obrazu czy rodzaj archiwum

(offline/online). Przykładowy fragment raportu wygenerowanego przez db2ckbkp pokazany jest na Listingu 2. Podanie opcji –h (mała litera „h”) przygotuje dokładnie ten sam raport, przy czym dodatkowo sprawdzi spójność obrazu, bez ko-nieczności odtworzenia bazy danych.

W tym miejscu chciałbym odnieść się do stwierdzenia z początku artykułu, że backup z narzędziami DB2 jest najlepszą formą zabez-pieczania danych. Przede wszystkim gwarantu-je nam spójność oraz niezmienialność danych, co nie zawsze jest łatwe do uzyskania np. w przy-padku eksportu danych. Porównując z bezpo-średnią kopią systemu plików, backup zawsze zajmuje mniej miejsca, ponieważ zawiera tylko te strony, które faktycznie zawierają dane. Jest jeszcze jedna bardzo użyteczna właściwość bac-kup-u – podczas odtwarzania mamy możliwość przedefiniowania kontenerów. Funkcjonalność ta jest szczególnie użyteczna, jeśli odtwarzamy bazę danych na innym komputerze, na którym nie ma utworzonej dokładnie takiej samej struk-tury systemu plików. Innym przypadkiem, w którym może wystąpić konieczność przedefinio-wania ścieżek kontenerów, jest klonowanie bazy

Listing 1. Skrypt wykonujący backup bazy w trybie OFFLINE

connect to sample;

quiesce database immediate force connections;

connect reset;

deactivate db sample;

backup database sample to "c:\temp";

connect to sample;

unquiesce database;

connect reset;

Listing 2. Sprawdzenie pliku archiwum narzędziem db2ckbkp

db2ckbkp -H SAMPLE.0.DB2.NODE0000.CATN0000.20090503224103.001

Server Database Name -- SAMPLE

Timestamp -- 20090504013219

Instance -- DB2

[..]

Backup Mode -- 0

Includes Logs -- 0

Compression -- 1

DB Codeset -- UTF-8

LogPath

-- C:\DB2\NODE0000\SQL00001\SQLOGDIR\

The proper image file name would be:

SAMPLE.0.DB2.NODE0000.CATN0000.20090504013219.001

Listing 3. Przykłady tworzenia obszarów tabel ze względnymi ścieżkami

create tablespce obszar1

create tablespce obszar2 managed by automatic storage

create tablespace obszar3 managed by database using (file 'obszar3' 10000)

autoresize yes

create tablespace obszar4 managed by system using ( 'obszar4')

Page 62: Sdj Extra 35 Db2

62

Administracja

SDJ Extra 35

w ramach tego samego komputera – kopia bazy będzie musiała wykorzystać inny zestaw plików, tak by pliki dwóch baz danych nie ulegały wza-jemnemu nadpisywaniu. Poniższe dwa polece-nia pozwolą na szybkie powielenie przykładowej bazy danych SAMPLE. W rezultacie powstanie no-wa baza danych TESTDB, która ma dokładnie taką samą zawartość jak baza SAMPLE:

db2 backup db sample

db2 restore db sample into testdb

Obydwa polecenia wykonają się poprawnie, za-kładając, że były wykonywane z tego samego ka-talogu oraz w katalogu tym nie znajdował się wcześniej żaden obraz archiwum bazy SAMPLE. Sklonowanie bazy danych przykładów, która przychodzi z instalacją DB2, powiedzie się bez konieczności powtórnego definiowania ścieżek kontenerów, ponieważ baza SAMPLE zawiera wy-łącznie względne ścieżki kontenerów. Względ-ne ścieżki kontenerów wykorzystywane są dla wszystkich domyślnych obszarów tabel po-wstałych przy kreowaniu bazy danych (CREATE DATABASE), obszarów AUTOMATIC STORAGE oraz obszarów, dla których po prostu nie podano bezwzględnej ścieżki, tak jak na Listingu 3.

W większości dużych i wymagających baz danych stosuje się jednak bezwzględne ścież-ki do plików obszarów tabel, ponieważ takie obszary dają administratorowi precyzyjną kontrolę nad położeniem pliku, nazwą, roz-miarem oraz ewentualnym wykorzystaniem mechanizmu powiększania plików. Załóż-my, że w przykładowej bazie danych admi-nistrator utworzył dodatkowy obszar tabel TESTSPACE poleceniem jak na Listingu 4.

Utworzenie klonu bazy danych zawierają-cej obszar TESTSPACE w tym samym środowi-sku będzie wymagało już przedefiniowania kontenerów bazy, ponieważ pliki c:\DATA1\testspace.1 oraz c:\DATA2\testspace.2 nie mogą jednocześnie być używane przez dwie bazy danych. Taki rodzaj odtworzenia bazy da-nych określany jest jako „odtwarzanie z prze-kierowaniem kontenerów” (ang. redirected re-store). Odtworzenie bazy danych bez prze-kierowania kontenerów zakończyłoby się po-prawnie, jednak obszar tabel TESTSPACE nie zostałby odtworzony, ponieważ DB2 wykry-łoby, że pliki są już wykorzystywane przez in-ną bazę (tu: SAMPLE). Na Listingu 5 załączyłem skrypt, który odtwarza bazę danych pod nową nazwą TESTDB (dokładniej klonuje bazę) z jed-

noczesnym przedefiniowaniem kontenerów. Klauzula REDIRECT wykorzystana w poleceniu RESTORE instruuje narzędzie, że administra-tor odtwarzający bazę danych będzie powtór-nie definiował ścieżki do kontenerów. Od te-go momentu odtwarzanie bazy jest wstrzyma-ne aż do czasu, gdy zostanie wydane polecenie kontynuacji odtwarzania (klauzula CONTINUE), które rozpocznie właściwe wgrywanie obrazu backup-u. Zdefiniowanie nowych ścieżek dla kontenerów realizowane jest poleceniem SET TABLESPACE CONTAINERS, w którym nale-ży podać identyfikator obszaru tabel. W mo-jej przykładowej bazie danych obszar TESTDB miał identyfikator o numerze 7. Po zainicjo-waniu odtwarzania z przekierowaniem za-wsze możesz sprawdzić identyfikatory obsza-rów tabel oraz kontenery danych obszarów ta-bel poleceniami LIST TABLESPACES oraz LIST TABLESPACE CONTAINERS FOR <nr obszaru>, czyli innymi słowy możesz interaktywnie od-pytywać obraz archiwum. W załączonym przykładzie założyłem, że cała operacja wy-kona się ze skryptu, stąd też dodałem klauzu-lę WITHOUT PROMPTING, która zakłada brak in-terakcji z użytkownikiem. Gdybym nie podał tej opcji w sytuacji, gdyby baza danych TESTDB byłaby już utworzona, narzędzie RESTORE wstrzymałoby pracę i poprosiłoby o decyzję, czy nadpisać istniejącą bazę.

Łatwo sobie wyobrazić, że operacja odtwa-rzania z przekierowaniem może przysporzyć wiele pracy, jeśli baza danych składa się z bar-dzo dużej liczby obszarów tabel (np. w sys-temach SAP, gdzie wykorzystywanych mo-że być nawet kilkadziesiąt kontenerów). W takich przypadkach najlepiej przygotować skrypt, który będzie zawierał wszystkie po-trzebne instrukcje definiujące kontenery ba-zy danych. Taki skrypt można automatycznie wygenerować poniższym poleceniem:

db2 restore db sample into testdb redirect

generate script testdb.db2

Wygenerowany skrypt (Listing 6) będzie po-dobny do tego, który już wcześniej posłużył nam do odtworzenia bazy danych w Listin-gu 5, z tą różnicą, że kontenery będą odnosiły się do plików aktualnie wykorzystywanych w bazie SAMPLE. Przed uruchomieniem skryptu należy oczywiście zmienić ścieżki kontene-rów, tak by były poprawne w nowym środo-wisku. Wygenerowany automatycznie skrypt znacznie ułatwia odtwarzanie z przekiero-waniem, ponieważ proponuje instrukcje SET TABLESPACE CONTAINERS wyłącznie dla obsza-rów tabel, które mogą tego wymagać: przykła-dowo pomija obszary automatycznie zarzą-dzanych przez bazę danych. Wygenerowany automatycznie skrypt zawiera także dodat-kowe informacje opisujące wszystkie obszary tabel, takie jak rozmiar obszaru, rozmiar stro-ny, znacznik włączenia automatycznego po-

Listing 4. Przykład utworzenia obszaru tabel z bezwzględnymi ścieżkami

create tablespace testspace managed by database

using (file 'c:\DATA1\testspace.1' 10000, file 'd:\DATA2\testspace.2' 10000)

autoresize yes maxsize 1 G;

create table testtab (id int) in testspace;

insert into testtab values (1), (2), (3), (4), (5);

Listing 5. Odtworzenie bazy danych z przekierowaniem kontenerów

restore db sample into testdb redirect without prompting;

set tablespace containers for 7 using (file 'testdb.testspace' 20000);

restore db sample continue;

Listing 6. Fragment skryptu wygenerowanego klauzulą RESTORE REDIRECT GENERATE SCRIPT

-- ** Tablespace name = TESTSPACE

-- ** Tablespace ID = 7

-- ** Tablespace Type = Database managed space

-- ** Tablespace Content Type = All permanent data. Large table

space.

-- ** Tablespace Page size (bytes) = 8192

-- ** Tablespace Extent size (pages) = 32

-- ** Using automatic storage = No

-- ** Auto-resize enabled = Yes

-- ** Total number of pages = 20000

-- ** Number of usable pages = 19904

-- ** High water mark (pages) = 160

-- *****************************************************************************

SET TABLESPACE CONTAINERS FOR 7

USING (

FILE 'c:\DATA1\testspace.1' 10000,

FILE 'd:\DATA2\testspace.2' 10000

);

Page 63: Sdj Extra 35 Db2

Backup od A do Z

63www.sdjournal.org

większania plików (AUTORESIZE) czy znacz-nik automatycznego zarządzania obszarem (AUTOMATIC STORAGE).

Backup offline ma jedną wielką zaletę – po-nieważ zawiera spójny obraz bazy danych, dla-tego odtwarzanie bazy danych z takiego archi-wum jest bardzo proste. Backup offline ma jed-nak także jedną znaczną wadę – jego wykona-nie wymaga odłączenia użytkowników od ba-zy. W wielu systemach, które pracują w trybie ciągłym 24x7, po prostu nie ma możliwości wstrzymania pracy użytkowników. W takich przypadkach z pomocą przychodzi archiwum online, które może być wykonywane na aktyw-nej bazie danych. By wykonać backup podczas normalnej pracy bazy danych, w poleceniu BACKUP należy dodać słowo kluczowe online:

db2 backup db sample online

Powyższe polecenie powiedzie się jednak tyl-ko wtedy, gdy dziennik transakcji bazy danych jest odpowiednio skonfigurowany do pracy w trybie archiwalnym (ang. archive logging).

Konfiguracja dziennika transakcjiDziennik transakcji w DB2 to zestaw plików, do których baza danych zapisuje wszystkie in-formacje o modyfikacjach danych. To właśnie dzięki dziennikowi transakcji możliwe jest wycofywanie transakcji (rollback) oraz auto-matyczne uspójnianie bazy danych po awarii (crash recovery). Pliki dziennika transakcji alo-kowane są na dysku w momencie aktywacji bazy danych. Liczba plików dziennika okre-ślona jest przez parametr bazy LOGPRIMARY, natomiast rozmiar pojedynczego pliku przez parametr LOGFILSIZ (wyrażony jest w stro-nach 4KB). Ustawienie parametrów dzienni-ka transakcji na następujące wartości:

LOGFILSIZ = 10000

LOGPRIMARY = 30

oznacza, że przy starcie bazy danych zosta-nie zaalokowane około 1.2 GB logów w pli-kach o automatycznie generowanych na-zwach S0000000.LOG, S0000001.LOG, S0000002.LOG, …, S0000029.LOG. Oczy-wiście pliki dziennika nie są alokowane przy każdorazowej aktywacji bazy danych, a je-dynie za pierwszym razem albo gdy zosta-ły zmienione parametry dziennika, takie jak rozmiar pliku, liczba plików czy ścieżka pli-ków. Istnieje jeszcze jeden parametr mają-cy wpływ na liczbę zaalokowanych plików dziennika – LOGSECOND. Parametr ten okre-śla liczbę plików, które mogą być dodatko-wo przydzielone, jeśli w podstawowym ob-szarze dziennika określanym przez parametr LOGPRIMARY brakuje już miejsca, by obsłużyć bieżącą transakcję. Dodatkowe logi alokowa-ne są dynamicznie, zależnie od zapotrzebowa-

nia na miejsce w dzienniku transakcji aż do osiągnięcia limitu łącznej liczby logów okre-ślonych przez sumę LOGPRIMARY i LOGSECOND. Innymi słowy, dostępny dla transakcji obszar roboczy dziennika wyznaczony jest przez su-mę dzienników podstawowych LOGPRIMARY oraz dodatkowych LOGSECOND. Jedyna różni-ca pomiędzy tymi typami logów związana jest z momentem przydzielania i zwalniania dla nich miejsca.

Domyślnie po utworzeniu bazy danych dzien-nik transakcji pracuje w trybie cyklicznym, tzn. transakcje zapisywane są najpierw do pierwsze-go pliku dziennika, potem do drugiego, trzeciego itd. Po zapisaniu ostatniego pliku dziennika ba-za znów zaczyna pisać do pierwszego pliku i cały proces powtarza się aż w nieskończoność.

W trybie archiwalnym pliki dziennika nig-dy nie są nadpisywane. Gdy zostanie zapisany pierwszy plik dziennika, baza zaczyna pisać do następnego w kolejności pliku i jednocześnie rozpoczyna przydzielanie miejsca dla nowe-go pliku o najwyższym kolejnym numerze. Ta-ka organizacja dzienników transakcji pozwala przechować pełną historię operacji na bazie da-nych. Archiwalne dzienniki transakcji w połą-czeniu z obrazem backup-u pozwalają na od-tworzenie bazy danych do dowolnego punk-tu w czasie. Odtworzenie bazy danych z wyko-rzystaniem archiwalnych dzienników transak-cji składa się z dwóch faz:

• Restore – odtworzenia archiwum (online bądź offline);

• Rollforward – powtórzenia transakcji z dzienników do określonego punktu w czasie.

Poniżej przedstawiłem przykład odtworze-nia bazy danych do punktu w czasie:

db2 restore db sample

db2 rollforward db sample to 2009-04-03-

14.21.56 using local time and stop

Do wykonania operacji rollforward potrzebne będą pliki dziennika – stąd prosty wniosek, że pliki dziennika powinny być odpowiednio za-bezpieczone, tak by były pod ręką podczas od-twarzania bazy. DB2 można tak skonfigurować, by pliki dziennika były automatycznie archiwi-zowane. Konfigurację dziennika transakcji naj-prościej wykonać, wykorzystując DB2 Control Center: uruchom narzędzie, wpisując db2cc, zaznacz bazę danych, którą chcesz konfiguro-wać, kliknij prawym klawiszem i wybierz „Con-figure Database Logging…”. Na pierwszym ekranie wybierz tryb archiwalny dziennika. Na następ-nym ekranie masz możliwość skonfigurowania metody archiwizacji dziennika. Zalecaną meto-dą archiwizowania plików dziennika jest zlece-nie tego zadania samej bazie danych, tak jak po-kazałem to na Rysunku 1. Pliki dziennika moż-na składować na dysku z wykorzystaniem IBM Tivoli Storage Manager (TSM) bądź przy pomo-cy dowolnego wspieranego oprogramowania backup-owego (Vendor DLL). Jeśli ze względów kosztowych nie będziesz dysponował bibliote-ką taśmową i wybierzesz dysk, upewnij się, że archiwalne pliki dziennika są składowane na innym fizycznym dysku, niż baza danych. Ta-kie podejście zapewni większe bezpieczeństwo logów – w przeciwnym przypadku ewentual-na awaria dysków bazy pociągnie za sobą także awarię archiwum dziennika transakcji.

Plik dziennika transakcji jest automatycznie archiwizowany zaraz po jego zapełnieniu. Ła-two dojść do wniosku, że rozmiar pliku dzien-nika pośrednio wpływa na bezpieczeństwo da-nych. Bardzo duży plik oznacza, że będzie on archiwizowany stosunkowo rzadko, ponieważ

Rysunek 1. Ekran konfiguracji archiwalnego trybu dziennika transakcji

Page 64: Sdj Extra 35 Db2

64

Administracja

SDJ Extra 35

może minąć dużo czasu zanim zostanie on za-pełniony. Natomiast zbyt mały plik może ne-gatywnie wpłynąć na wydajność ze względu na bardzo częste odwoływanie się do systemu backup-owego. Rozmiar pliku dziennika najle-piej dopasować do aktywności systemu, tak by przykładowo pomieścił transakcje za 30 mi-nut przetwarzania. Baza danych automatycz-nie usuwa zarchiwowane pliki dziennika z bie-żącego katalogu, jednak dopiero wtedy, gdy już nie są potrzebne do bieżącego przetwarzania. Jeśli dziennik zawiera aktywną (niezatwier-dzoną) transakcję, wtedy może być potrzeb-ny do wycofania zmian, gdyby aplikacja zde-cydowała się wykonać instrukcję rollback. Na Rysunku 1 przedstawiłem przykładowy ekran konfiguracji dziennika transakcji narzędzia DB2 Control Center – dla konfigurowanej bazy archiwalne pliki dziennika będą przeno-szone na dysk do katalogu D:\DB2_ARCH_LOGS (Primary archive log path). Gdyby okazało się, że podstawowe miejsce archiwizowania dzien-nika jest niedostępne (np. system plików jest zapełniony; w przypadku biblioteki taśmowej może to oznaczać chwilową niedostępność związaną z pozycjonowaniem taśmy), wtedy plik dziennika będzie tymczasowo przenie-siony do katalogu C:\DB2_TEMP_ARCH_LOGS (Failure archive log path). Plik ten będzie auto-matycznie umieszczony we właściwym archi-wum, jak tylko baza wykryje, że podstawowe archiwum jest już dostępne.

Na następnych ekranach można doprecyzo-wać pozostałe parametry dziennika oraz para-metry backup-u. Na ostatnim ekranie narzę-dzia konfigurującego znajduje się podsumo-wanie wykonywanych operacji. Warto przej-rzeć wygenerowany skrypt (zakładka Suma-ry->przycisk Show Command), by zapoznać się z parametrami DB2, które odpowiadają za konfigurację dziennika transakcji. Za włącze-

nie archiwalnego trybu pracy dziennika trans-akcji odpowiada parametr LOGARCHMETH1. Pa-rametr ten jednocześnie definiuje metodę ar-chiwizacji. Poniżej przedstawiłem instrukcję zmiany parametrów odpowiadającą wprowa-dzonym wartościom z Rysunku 1:

UPDATE DB CFG FOR SAMPLE USING

logarchmeth1 "DISK:D:\DB2_ARCH_

LOGS" failarchpath "C:\DB2_TEMP_

ARCH_LOGS"

Po zmianie trybu pracy dziennika transak-cji z cyklicznego na archiwalny baza danych wymusza zrobienie archiwum offline. Jeśli nie wykonamy backup-u, DB2 nie pozwoli podłączyć się do bazy danych, generując ko-munikat, że baza danych jest w stanie ocze-kiwania na backup:

SQL1116N A connection to or activation

of database "SAMPLE" cannot be

made because of BACKUP PENDING.

SQLSTATE=57019

Bez początkowego punktu odniesienia nie da się odtworzyć zmian zapisanych w dzienni-kach transakcji. Archiwalne dzienniki trans-akcji są bezużyteczne bez backup-u, stąd ko-nieczność wykonania archiwum całościowego. Istnieje możliwość rozpoczęcia pracy z bazą bez konieczności wykonywania backup-u. Na-rzędziem db2dart możesz wyłączyć stan ocze-kiwania na backup (BACKUP PENDING), a archi-wum całościowe wykonać w trybie online przy pracujących użytkownikach. Pamiętaj jednak, by nie wykonywać takiej operacji na systemie produkcyjnym, gdyż do momentu zakończe-nia backup-u wprowadzane transakcje nie bę-dą zabezpieczone. Ponieważ na potrzeby napi-sania tego artykułu pracuję na testowej bazie

danych, więc po włączeniu archiwalnego try-bu wykorzystałem narzędzie db2dart do usu-nięcia znacznika BACKUP PENDING.

db2dart sample /CHST /WHAT DBBP OFF

Po pomyślnym wykonaniu powyższego po-lecenia użytkownicy będą mogli już praco-wać na bazie danych.

By zademonstrować wykorzystanie backup-ów online, przygotowałem skrypt do załadowa-nia przykładowej tabeli tab1 milionem rekor-dów (Listing 7). Skrypt uruchomiłem z jednego okna linii poleceń DB2 (db2 –td@ –vf nazwa_skryptu), natomiast z drugiego okna urucho-miłem backup w trybie online (db2 backup db sample online compress). Backup bazy danych wykonał się równolegle z procesem ładowania danych. Plik backup-u online odzwierciedla stan bazy danych na określony punkt w czasie i jest swojego rodzaju migawką (ang. snapshot) da-nych. Baza danych pilnuje, by w obrazie archi-wum nie znalazły się strony zmodyfikowane już po uruchomieniu backup-u (dokładniej: strony starsze, niż znacznik czasowy archiwum). Jeśli któraś ze stron zostanie napisana nową zawarto-ścią, wtedy DB2 sięga do dziennika transakcji, by stamtąd pobrać starszą wersję strony.

Podczas ładowania danych dzienniki trans-akcji były sukcesywnie zapełniane i przenoszo-ne do archiwum, czyli w naszym przypadku do katalogu D:\DB2_ARCH_LOGS. Tak napraw-dę DB2 w katalogu wskazywanym przez pa-rametr LOGARCHMETH1 tworzy szereg dodatko-wych podkatalogów, by jednoznacznie iden-tyfikować logi, gdyby z tej samej ścieżki archi-wum korzystało wiele baz danych. Pełna ścież-ka archiwalnych logów ma postać:

D:\DB2_ARCH_LOGS\DB2\SAMPLE\NODE0000\

C0000000

Na tej ścieżce znajdować się będą pliki archi-walnych dzienników:

...

2009-05-11 23:56 4 104 192 S0000006.LOG

2009-05-11 23:56 4 104 192 S0000007.LOG

2009-05-11 23:56 4 104 192 S0000008.LOG

2009-05-11 23:57 4 104 192 S0000009.LOG

...

Zarchiwizowane dzienniki transakcji peł-nią kluczową rolę przy odtwarzaniu backup-u wykonanego w trybie online. Tak naprawdę bez zarchiwowanych dzienników transakcji backup jest bezużyteczny! Dlaczego? Rezulta-tem wykonania polecenia RESTORE DATABASE jest utworzenie bazy danych, której stan jest dokładnym odzwierciedleniem stanu bazy z momentu wykonania archiwum. Backup on-line wykonywaliśmy równolegle z aplikacją ła-dującą dane, co oznacza, że odtworzona baza danych jest niespójna, ponieważ zawiera nie-

Listing 7. Skrypt ładujący przykładową tabelę TAB1 milionem rekordów

CREATE TABLE tab1 (id INT, czas TIMESTAMP)@

CREATE PROCEDURE laduj_tab1()

BEGIN

DECLARE i INT DEFAULT 0;

-- Zatwierdź długą transakcję, gdy wystąpi komunikat zapełnienia dziennika

DECLARE CONTINUE HANDLER FOR SQLSTATE '57011'

COMMIT;

WHILE (i < 1000000)

DO

INSERT INTO tab1 VALUES ( i, current timestamp);

SET i = i + 1;

END WHILE;

COMMIT;

END @

CALL laduj_tab1() @

Page 65: Sdj Extra 35 Db2

Backup od A do Z

65www.sdjournal.org

zatwierdzone transakcje. Próba podłączenia się skutkuje błędem, informującym że baza danych jest w stanie oczekiwania na wykona-nie operacji ROLLFORWARD.

db2 restore db sample replace existing

db2 connect to sample

SQL1117N A connection to or activation of

database "SAMPLE" cannot be made

because of ROLL-FORWARD PENDING.

SQLSTATE=57019

W takiej sytuacji tylko przy pomocy dzien-ników transakcji będzie można uspójnić ba-zę i udostępnić ją użytkownikom. Wykona-nie polecenia:

db2 ROLLFORWARD DB SAMPLE TO END OF LOGS

odczyta wszystkie dostępne archiwalne pli-ki logów i powtórnie wykona zapisane w nich transakcje. W składni polecenia ROLLFORWARD nie musiałem podawać lokalizacji odtwarza-nych dzienników, ponieważ jest ona zapisana w konfiguracji bazy, która także jest odtwa-rzana podczas polecenia RESTORE. Wykona-nie polecenia ROLLFORWARD może być powta-rzane wielokrotnie. Takie podejście często sto-suje się przy migracjach bazy na inny, wydaj-niejszy komputer (w ramach tego samego sys-temu operacyjnego), by zminimalizować czas przestoju związany z przełączeniem aplika-cji na nowy system. Na pierwotnym systemie wykonujemy backup online, który wgrywamy na system docelowy. W tym czasie na źródło-wym systemie odkładają się dzienniki transak-cji, które na bieżąco przenosimy do docelowe-go systemu i wgrywamy ich zawartość, wielo-krotnie wykonując operację ROLLFORWARD. Po zatrzymaniu bazy źródłowej wystarczy prze-nieść i dograć tylko ostatni log (logi), a następ-nie podłączyć użytkowników do bazy w no-wym środowisku. Czas przestoju aplikacji jest równy czasowi wgrywania ostatniego logu. Przy wielokrotnym wgrywaniu logów bardzo pomocne jest polecenie ROLLFORWARD z klauzu-lą QUERY STATUS pokazujące numer następ-nego logu, który będzie przetwarzany przy ko-lejnym wykonaniu polecenia ROLLFORWARD (Li-sting 8).

Jeśli chcemy zakończyć operację dogry-wania kolejnych logów i udostępnić bazę da-nych użytkownikom, musimy wydać polece-nie ROLLFORWARD z klauzulą STOP bądź rów-noważną klauzulą COMPLETE. Przed udostęp-nieniem bazy do normalnej pracy wszystkie niezatwierdzone transakcje zostaną automa-tycznie wycofane, tak by baza była spójna. In-formacje potrzebne do wycofania transakcji zostaną pobrane z wcześniej wykorzystanych archiwalnych dzienników transakcji. Jak wi-dać, archiwalne dzienniki transakcji pełnią bardzo ważną rolę, ponieważ bez nich nie da się uspójnić bazy odtworzonej z backup-u on-

line. Backup online bez dzienników transakcji jest bezwartościowy, ponieważ tylko przy uży-ciu logów można wyprowadzić bazę ze stanu ROLL-FORWARD PENDING.

Utrata logów mogłaby wiązać się z jedno-czesną utratą backup-ów online. By zabezpie-czyć się przed taką sytuacją, DB2 standardo-wo dołącza pliki dziennika do obrazu bac-kup-u. Musisz jednak pamiętać, że takie za-chowanie bazy domyślnie dostępne jest od wersji 9.5. We wcześniejszych wersjach, np. 9.1 czy 8.2, logi należy dołączać w jawny spo-sób poprzez użycie klauzuli INCLUDE LOGS:

db2 backup db sample online include logs

Po wykonaniu tego polecenia pliki dziennika potrzebne do odtworzenia bazy danych (na moment zakończenia wykonywania całościo-wego archiwum) zostaną dokopiowane do ob-razu archiwum. Dołączenie logów zawierają-cych aktywne transakcje jest zupełnie nieza-leżne od mechanizmu archiwacji dziennika transakcji. W rezultacie aktywne dzienniki ko-piowane są w dwa miejsca. Odpowiednią klau-zulą LOGTARGET polecenia RESTORE możesz od-zyskać pliki dziennika dołączone wcześniej do obrazu archiwum. Zakładając, że archiwum logów nie jest dostępne (np. utraciliśmy archi-wum logów, ale mamy backup bazy online), od-tworzenie bazy online wygląda następująco:

db2 restore db sample logtarget c:\temp

db2 rollforward db sample to end of logs

and stop overflow log path (c:

\temp)

Pierwsze polecenie odtworzy bazę i jedno-cześnie przekopiuje dzienniki transakcji za-warte w obrazie archiwum do katalogu c:\temp. Drugie polecenie przetworzy wszystkie dzienniki transakcji (klauzula to TO END OF LOGS) z katalogu c:\temp (klauzula OVERFLOW LOG PATH) i udostępni bazę użytkownikom (klauzula AND STOP). W przypadku backup-ów wykonanych w trybie offline istnieje moż-liwość udostępnienia bazy danych użytkow-nikom bez konieczności wykonywania pole-cenia ROLLFORWARD. W tym celu należy wyko-rzystać klauzulę WITHOUT ROLLING FORWARD:

db2 restore db sample without rolling

forward

Odzyskanie bazy jednym poleceniemDla uproszczenia odtwarzania bazy danych po awarii dostępne jest także polecenie RECOVER DATABASE, które automatyzuje proces dogry-wania transakcji z dzienników. Poniższe po-lecenie automatycznie sięgnie po odpowied-ni obraz backup-u oraz po odpowiednie pliki dziennika transakcji, tak by przywrócić bazę danych na określony punkt w czasie:

db2 recover database baza1 to 2009-05-13-

09.07.51.000000 using local time

Polecenie RECOVER wykorzystuje systemowy plik bazy danych Recovery History File, w któ-rym przechowywana jest historia operacji ad-ministracyjnych. Poleceniem list history możesz sprawdzić, kiedy były wykonywane

Listing 8. Sprawdzenie stanu przetworzonych logów dla operacji ROLLFORWARD

db2 rollforward db sample query status

Rollforward Status

Input database alias = sample

Number of nodes have returned status = 1

Node number = 0

Rollforward status = DB working

Next log file to be read = S0000015.LOG

Log files processed = S0000000.LOG – S0000015.LOG

Last committed transaction = 2009-05-13-09.07.51.000000 UTC

DB20000I The ROLLFORWARD command completed successfully.

Listing 9. Dostęp do historii operacji administracyjnych z widoku systemowego

select eid, operation, start_time from sysibmadm.db_history

EID OPERATION START_TIME

--- --------- --------------

1 O 20090511235132

2 A 20090511235132

3 X 20090511235637

4 B 20090511235638

5 R 20090514000647

6 F 20090523145136

Page 66: Sdj Extra 35 Db2

66

Administracja

SDJ Extra 35

operacje administracyjne na bazie danych. Przykładowo, poniższe polecenie pokaże, kie-dy i na jaki nośnik były archiwowane poszcze-gólne dzienniki transakcji:

db2 list history archive log since 200905

for sample

Zawartość pliku z historią można także po-dejrzeć, sięgając do widoku systemowego SYSIBMADM.DB _ HISTORY. Przykładowo, po-niższa instrukcja SQL poda czasy rozpoczę-cia operacji administracyjnych, rodzaj opera-cji (O – usunięcie obszaru tabel, A – dodanie obszaru tabel, X – archiwizacja logu, B – bac-kup bazy, R – odtworzenie bazy, F – operacja powtórzenia transakcji z logów) oraz identyfi-kator wpisu do pliku historii (patrz Listing 9).

Plik z historią operacji administracyjnych jest także zawarty w obrazie backup-u ba-zy danych. Odpowiednią opcją polecenia RESTORE można odtworzyć plik historii. W przykładzie poniżej aktualnie używany plik z historią zostanie nadpisany plikiem wyodręb-nionym z obrazu backup-u. Dodanie słowa kluczowego ONLINE pozwoli podmienić plik historii w trakcie pracy bazy danych:

db2 restore db sample history file online

Wykorzystanie tego polecenia może okazać się przydatne, gdy odtwarzamy obraz bac-kup-u do istniejącej bazy danych. W takiej sytuacji DB2 standardowo zachowuje plik z historią znajdujący się na dyskach. Gdy-by okazało się, że jest on uszkodzony, wtedy powyższe polecenie pozwoli przywrócić je-go zawartość. Plik z historii można nadpisać także podczas odtwarzania bazy danych:

db2 restore db sample replace history file

Jednak lepiej jest zachować istniejący plik na dyskach bazy, ponieważ zawiera on komplet

informacji, w szczególności zawiera informa-cje o zdarzeniach, które zostały wykonane już po wykonaniu backup-u. Porównując skład-nię obu poleceń, warto zwrócić uwagę, że do-danie słowa REPLACE zupełnie zmienia sens polecenia. W takim przypadku odtwarzana jest zarówno baza danych, jak i plik z historią.

Odtwarzanie bazy w trybie onlineKonieczność odtworzenia bazy danych z ar-chiwum może oznaczać określoną przerwę w pracy systemu. Jeśli zbyt rzadko będziesz wykonywał archiwum całościowe, wtedy od-tworzenie bazy danych może wydłużyć się ze względu na konieczność przetworzenia dużej ilości archiwalnych dzienników. Przygotowu-jąc odpowiednią strategię archiwacji, możesz wykorzystać nie tylko archiwum całościowe, ale także archiwum danego obszaru tabel, ar-chiwa przyrostowe, delty oraz archiwizowane na bieżąco dzienniki transakcji.

W celu przyspieszenia odtwarzania bazy da-nych istnieje możliwość odtworzenia na począ-tek tylko wybranych obszarów tabel. Po uru-chomieniu kluczowych aplikacji, które pracu-ją na tabelach z tych obszarów, możesz odtwo-rzyć pozostałe obszary już w trakcie pracy ba-zy danych (odtwarzanie online), a następnie do-grać modyfikacje z dziennika transakcji. Wybra-nie takiej strategii odtwarzania wymaga jednak przemyślanego podziału bazy danych na logicz-ne obszary, na których aplikacje mogą praco-wać niezależnie od siebie (np. dane bieżące i da-ne z zeszłych lat). W dokumentacji DB2 Infor-mation Center znajdziesz wiele scenariuszy od-twarzania danych online (szukaj po słowie klu-czowym REBUILD oraz RESTORE TABLESPACE). Odtwarzanie online zademonstruję na przykła-dzie bazy danych SAMPLE, która ma włączoną archiwizację dziennika transakcji. W tym ce-lu uruchomię dwa okna linii poleceń DB2.W pierwszym będę wykonywał operacje admini-stracyjne, a w drugim instrukcje SQL.

Najpierw w pierwszym oknie wykonałem archiwum całościowe bazy danych:

db2 backup db sample online compress

W moim systemie archiwum zostało opatrzo-ne znacznikiem czasowym 20090528141351, który umownie nazwałem TS1. W archiwum znalazły się wszystkie obszary tabel, włącza-jąc obszar USERSPACE1, IBMDB2SAMPLEREL oraz systemowy słownik bazy danych (ang. system catalog). Następnie z drugiego okna utworzyłem nowy obszar tabel OBSZAR1, a w nim nową tabelę tab1, którą załadowałem da-nymi z tabeli org. Tabela org jest jedną z przy-kładowych tabel bazy SAMPLE i znajduje się w obszarze tabel USERSPACE1.

db2 create tablespace obszar1

db2 create table tab1 like org in obszar1

db2 insert into tab1 select * from org

Po załadowaniu tabeli wykonałem backup online obszaru OBSZAR1 oraz backup obsza-ru systemowego SYSCATSPACE, który zawie-ra wszystkie definicje obiektów.

db2 backup db sample tablespace(obszar1,sy

scatspace) online compress

Archiwum zostało opatrzone znacznikiem czasowym 20090528182420, który umow-nie nazwałem TS2.

Następnie w drugim oknie linii poleceń uaktualniłem tabelę ORG oraz tabelę TAB1, by zasymulować pracę użytkowników wykona-ną już po zrobieniu backup-u obszaru tabel.

db2 update tab1 set location = 'Denver!'

where location = 'Denver'

db2 update org set location = 'Denver!'

where location = 'Denver'

Poszczególne kroki opisane w przykładzie zobrazowałem na Rysunku 2.

Następnie deaktywowałem bazę danych, by mieć gwarancję, że bieżący plik transak-cji zostanie przeniesiony do archiwum. Prze-niesienie do archiwum częściowo zapełnio-nego logu mogę także wymusić poleceniem ARCHIVE LOG, tak jak na przykładzie poniżej:

db2 archive log for database sample

Teraz spróbuję usunąć bazę danych polece-niem DROP DATABASE, a następnie odtworzyć ją z archiwum. Proces odtwarzania będzie za-leżał od przyjętych założeń. Na potrzeby tego ćwiczenia założyłem, że w obszarze OBSZAR1 znajdują się bieżące dane niezbędne do działa-nia mojej aplikacji, dlatego też obszar ten nale-ży odtworzyć w pierwszej kolejności. Założy-łem także, że pozostałe obszary tabel (USER-SPACE1 oraz IBMDB2SAMPLEREL) zawierają da-Rysunek 2. Strategia backup-u z wykorzystaniem backup-u obszaru tabel

SYSCATSPACE

USERSPACE1

IMBDB2SAMPLERE

SYSCATSPACE

OBSZAR 1

USERSPACE1

IMBDB2SAMPLERE

Madyfikacje tabeli wobszarach OBSZAR1 i

USERSPACE1

TS1 TS2

Sesja 1

Sesja 2

Czas

Backup onlineobszaru 1

Page 67: Sdj Extra 35 Db2

Backup od A do Z

67www.sdjournal.org

ne historyczne, dlatego też mogą być odtwo-rzone w późniejszym czasie.

Wykorzystując klauzulę REBUILD WITH TABLESPACE, odtwarzam OBSZAR1 oraz słow-nik systemowy SYSCATSPACE, bez którego żadna baza danych nie może funkcjonować:

db2 restore db sample rebuild with

tablespace (syscatspace, obszar1)

taken at 20090528182420

db2 rollforward db sample to end of logs

and stop

Po wykonaniu powyższych operacji tabela TAB1 z obszaru OBSZAR1 jest dostępna i zawie-ra modyfikacje wykonane po czasie TS2. Użyt-kownicy mogą już pracować z tą tabelą. Tabela ORG z obszaru USERSPACE1 jest natomiast cią-gle niedostępna i wymaga odtworzenia z bac-kup-u (pozostałe obszary tabel są już widocz-ne, jednak nie zawierają danych). Odtwarzanie reszty obszarów tabel może być już wykonane przy aktywnej bazie danych:

db2 restore db sample tablespace

(IBMDB2SAMPLEREL, USERSPACE1)

online taken at 20090528141351

db2 rollforward db sample to end

of logs and stop tablespace

(IBMDB2SAMPLEREL, USERSPACE1)

online

Po wykonaniu powyższych operacji tabe-la ORG będzie dostępna dla użytkowników. Kolejność wykonanych operacji odtwarzania bazy została przedstawiona na Rysunku 3.

Polecenie RESTORE z klauzulą REBUILD w razie potrzeby może odwoływać się także do wcześniej wykonanych backup-ów. W poda-nym przykładzie słownik systemowy został odtworzony bezpośrednio z backup-u identy-fikowanym znacznikiem TS2 (backup obsza-rów). Gdyby backup TS2 nie zawierał słowni-ka systemowego bazy danych, przedstawiony scenariusz odtwarzania także powiódłby się, przy czym słownik odtworzony byłby automa-tycznie z backup-u TS1, a wszystkie zmiany na słowniku byłyby naniesione z dziennika trans-akcji. By przyspieszyć proces odtwarzania, naj-lepiej jednak dołączyć słownik systemowy do backup-owanego obszaru tabel. Tak naprawdę do odtworzenia całej bazy danych wystarczy wskazać wyłącznie drugi backup TS2:

db2 restore db sample rebuild with all

tablespaces in database taken at

20090528182420

db2 rollforward db sample to end of logs

and stop

Narzędzie automatycznie sięgnie po poprzed-ni backup i odtworzy także pozostałe (IBMD-B2SAMPLEREL, USERSPACE1) obszary tabel. Ta-

kim wywołaniem jednak nie osiągniemy za-mierzonego efektu, by jak najszybciej udostęp-nić bazę użytkownikom. Proces odtwarzania jest prostszy, natomiast baza będzie dostępna dopiero po odtworzeniu wszystkich obszarów tabel znajdujących się w backup-ach TS1 i TS2.

W tym miejscu chciałbym zwrócić uwagę, że podany przykład odtwarzania online opie-rał się na założeniu, że odtwarzana baza da-nych jest usunięta w całości. Zależnie od rodza-ju awarii może zdarzyć się, że część plików ba-zy została usunięta, natomiast część pozosta-ła na dyskach. Narzędzie odtwarzające bazę na podstawie plików konfiguracyjnych bazy zasta-nych na dyskach jest w stanie określić, jakie pli-ki przynależały do uszkodzonej bazy danych, tak by można było je bezpiecznie nadpisać zawar-tością z backup-u. Gdyby pliki konfiguracyjne nie były dostępne (np. katalog SQL00001 został-by usunięty), wtedy może okazać się koniecz-ne ręczne wyczyszczenie katalogów zajmowa-nych przez poprzednią bazę danych (także ręcz-ne odkatalogowanie bazy poleceniem UNCATALOG DATABASE oraz usunięcie lokalnego wykazu baz SQLDBDIR). Takie zachowanie ma zapobiec nad-pisaniu plików, co do których baza nie ma pew-ności, że pochodzą od uszkodzonej bazy, a nie od innych baz, aplikacji czy użytkowników. W przy-padkach awarii systemu plików (awarii dysków) standardowo najpierw odtwarza się system ope-racyjny (np. binarna bazy), a następnie bazę da-nych z backup-u. Innym możliwym podejściem jest odtworzenie „czystego” systemu operacyjne-go, następnie powtórna instalacja oprogramowa-nia i dopiero na samym końcu odtworzenie bazy z backup-u. Takie podejście zajmuje jednak wię-cej czasu i stosowane jest stosunkowo rzadko.

Tivoli Storage ManagerZ awariami sprzętu najłatwiej poradzić sobie poprzez zastosowanie redundancji, czyli nad-miarowych elementów systemu. Jak jednak po-radzić sobie z błędami człowieka? Jak pokazują statystyki, czynnik ludzki to druga najczęstsza przyczyna utraty danych (patrz Rysunek 4).

Błędy administratora można zminimalizować poprzez podnoszenie kwalifikacji oraz poprzez zautomatyzowanie wszystkich powtarzalnych operacji, takich jak wykonywanie backup-ów. Harmonogramowanie backup-ów zgodnie z określonym kalendarzem możemy zrealizować, wykorzystując Centrum Zadań (Task Center). Uruchamianie backup-ów możemy także zlecić samej bazie danych. By skonfigurować automa-tyczne backup-owanie bazy, w narzędziu DB2 Control Center kliknij prawym klawiszem nad bazą danych i wybierz opcję automatycznej kon-serwacji (Configure Automatic Maintenance). Na odpowiednich ekranach narzędzia będziesz miał możliwość zdefiniowania reguł określających, jak często DB2 będzie wykonywało backup. Narzę-dzie pozwala także zdefiniować czasowe okno konserwacji bazy, w którym będą wyzwalane automatyczne operacje. Na Rysunku 5 przed-stawiłem przykładowy ekran konfiguracji au-tomatycznych backup-ów – w tym przypadku backup-y będą wykonywane nie rzadziej niż raz na dwa dni oraz na tyle często, by ilość transakcji odłożonych w logach pomiędzy backup-ami nie przekroczyła 100 MB.

W firmach, które muszą zarządzać kilkoma bazami danych, trudno wyobrazić sobie życie bez dedykowanego oprogramowania do auto-matyzacji backup-u, takiego jak IBM Tivoli Sto-rage Manager (TSM). Zadania realizowane przez TSM znacznie wybiegają poza funkcje obsługi bibliotek taśmowych. TSM automatycznie mon-tuje taśmy, dokonuje konsolidacji i defragmenta-cji danych na odpowiednich taśmach, sprawdza-jąc ich poprawność. TSM pozwala na scentralizo-wane backup-owanie zarówno baz danych, jak i systemów plików (zcentralizowane harmonogra-mowanie). Wykorzystanie TSM pozwala dużo szybciej odtworzyć bazę danych, ponieważ star-sze obrazy backup-ów mogą być automatycznie migrowane na wolniejsze nośniki (taśmy, dyski). TSM automatycznie zarządza także kluczami szyfrowania, co pozwala na odpowiednią ochro-nę backup-ów przed ewentualną kradzieżą no-śników, na których są one składowane.

Rysunek 3. Kolejność odtwarzania z wykorzystaniem backup-u obszaru tabel

USERSPACE1

IMBDB2SAMPLERE

SYSCATSPACE

OBSZAR 1

USERSPACE1

IMBDB2SAMPLERE

CzasRESTOREObszaru1 z

backupu TS2

RESTOREbackup-u TS1

ROLLFORWARDoperacji w Obszarze1wykonanych po TS2

Page 68: Sdj Extra 35 Db2

68

Administracja

SDJ Extra 35

Wykorzystanie TSM do backup-owania baz relacyjnych wymaga zastosowania kom-ponentu IBM Tivoli Storage Manager for Da-tabases. W przypadku DB2 ta funkcjonalność jest wbudowana w bazę danych (standardo-wa licencja DB2 oraz Informix Dynamic Se-rver obejmuje także licencję klienta TSM do wykonywania backup-ów bazy). Po skonfigu-rowaniu środowiska TSM backup wykonuje się, podając klauzulę USE TSM:

db2 backup db sample use tsm

Czytelnikom zainteresowanym dokładniej-szym rozpoznaniem środowiska TSM pole-cam publikacje RedBooks: Backing up DB2 with IBM Tivoli Storage Management (SG24-6247-00 ) oraz SAP Backup using Tivoli Stora-ge Manager (SG24-7686-00).

Backup bazy 10 TB i większejW przypadku bazy danych znacznych roz-miarów backup online może wykonywać się stosunkowo długo. Backup można przyspie-szyć, wykorzystując kilka urządzeń, na które ma być składowany (np. kilka urządzeń taśmo-wych bądź kilka dysków) i uruchamiając od-powiednią liczbę równoległych sesji (klauzula PARALLELISM polecenia BACKUP). DB2 dostar-cza także narzędzia do kontroli zasobów zuży-wanych przez backup, więc mamy możliwość ograniczania wpływu długo wykonującego się backup-u na bieżące przetwarzanie. Aktualny stan zaawansowania backup-u można wyświe-tlić poleceniem LIST UTILITIES SHOW DETAIL. Przykładowy wynik działania polecenia po-kazałem na Listingu 10. Warto zwrócić uwa-gę na element określający priorytet backup-u: wartość „Unthrottled” oznacza, że backup pra-cuje z pełną mocą. Priorytet backup-u można ograniczyć poleceniem:

db2 set util_impact_priority for 29 to 10

Po wykonaniu powyższego polecenia zada-nie o identyfikatorze 29 (backup z Listin-gu 10) będzie konsumowało do 10% dostęp-nych zasobów.

Co jednak zrobić w sytuacji, w której z jed-nej strony obniżenie priorytetu backup-u po-woduje jego wydłużenie do nieakceptowalne-go poziomu, a z drugiej strony podwyższenie priorytetu powoduje nieakceptowalne obcią-żenie bazy, negatywnie wpływające na czasy odpowiedzi? W takich przypadkach najlepiej wykorzystać technikę rozdzielenia zapisu lu-strzanego (ang. split mirror).

Na pracującej bazie danych wstrzymuje się wszystkie operacje zapisu na dysk, wykonu-jąc polecenie:

db2 connect to sample

db2 set write suspend for database

W tym stanie baza nie wykona żadnych ope-racji modyfikujących dane na dyskach, więc można bezpiecznie skopiować pliki bazy bez obawy, że ich zawartość zostanie podmie-niona w trakcie tej operacji. W stanie WRITE SUSPEND baza będzie akceptowała zapytania SQL, natomiast wszystkie instrukcje modyfi-kujące dane zostaną wstrzymane („zawisną”). Najszybsze skopiowanie plików bazy uzysku-je się poprzez odłączenie zapasowego zesta-wu dysków (kopii mirroring-u) i podłączenie ich do dodatkowego komputera – taką moż-liwość oferują nowoczesne modele macierzy dyskowych, a operacja rozdzielenia zapisu trwa bardzo krótko (pojedyncze sekundy).

Po wznowieniu zapisów użytkownicy mo-gą dalej pracować na bazie danych:

db2 set write resume for database

Tak wykonaną kopię plików bazy danych mo-żemy już backup-ować na taśmę z drugiego komputera bez obciążania podstawowego sys-temu. Procedura odtworzenia bazy danych po-lega na nadpisaniu plików uszkodzonej bazy plikami z tak zrobionego archiwum. Wykorzy-stując narzędzie db2inidb, zmieniamy stan sko-piowanych plików bazy danych, by można by-ło jeszcze dograć dzienniki transakcji. Po wy-konaniu poniższego polecenia baza danych znajdzie się w stanie ROLL-FORWARD PENDING:

db2inidb sample as mirror

W efekcie końcowym stan bazy odpowiada do-kładnie stanowi, jaki uzyskalibyśmy, odtwarza-jąc bazę z backup-u w tradycyjny sposób, a nie poprzez kopiowanie plików z poziomu syste-mu operacyjnego. Po dograniu logów baza da-nych jest już dostępna dla użytkowników:

db2 rollforward db sample to end of logs

and stop

Muszę jeszcze dodać ważną uwagę, że pod-czas kopiowania plików bazy z archiwum na-leży pominąć bieżące dzienniki transakcji, a w szczególności ostatni bieżący plik, który był częściowo zapełniony w momencie roz-dzielania mirroring-u. Pozwoli to uniknąć sy-tuacji, w której baza będzie widziała dwa pliki o tej samej nazwie, lecz o różnej zawartości (je-den w bieżącej lokalizacji częściowo zapełnio-ny, a drugi w archiwum zapełniony w całości).

Listę plików do skopiowania w stanie wstrzy-manych zapisów można uzyskać, odwołując się do odpowiedniego widoku systemowego:

db2 select * from sysibmadm.dbpaths

Od wersji DB2 9.5 mechanizm macierzo-wych backup-ów bazy danych został zin-tegrowany z określonymi modelami macie-rzy, co pozwala na pełną automatyzację te-go procesu. Czytelników zainteresowanych tą funkcjonalnością odsyłam do dokumen-tacji – DB2 Advanced Copy Services (ACS).

Odtworzenie usuniętej tabeliNa zakończenie artykułu chciałbym jeszcze omówić metodę odtworzenia tabeli usuniętej in-strukcją DROP TABLE. DB2 dostarcza specjalny mechanizm zabezpieczania danych usuniętych razem z tabelą. Mechanizm ten włącza się (wyłą-cza) niezależnie dla każdego z obszarów tabel:

db2 alter tablespace userspace1 dropped

table recovery on

Domyślnie, mechanizm ten jest włączony dla każdego z obszarów tabel w bazie. By odtworzyć usuniętą tabelę w pierwszym kroku należy z pliku historii pobrać iden-tyfikator usuniętej tabeli:Rysunek 4. Przyczyny utraty danych. Źródło: na podstawie badań Ontrack

44%

32%

14%

7% 3%

Awaria sprzętuBłąd człowieka

Błąd oprogramowaniaWirusy

Zniszczenia (np. pożar)

Page 69: Sdj Extra 35 Db2

Backup od A do Z

69www.sdjournal.org

db2 list history dropped table all for

sample

Identyfikator ma postać 0000000000007d2a00020004 i jedno-znacznie określa wersję tabeli w czasie (ta-bela o tej samej nazwie mogła być wielo-krotnie tworzona i usuwana).

W następnym kroku należy z backup-u od-tworzyć obszar tabel, w którym znajdowała się usunięta tabela. Najlepiej wykonać odtwa-rzanie w trybie online, by nie ograniczać do-stępności pozostałych obszarów:

db2 restore db sample

tablespace(userspace1) online

W ostatnim kroku należy wykonać operację ROLLFORWARD z klauzulą RECOVER DROPPED TABLE, tak jak poniżej:

db2 rollforward database sample to end

of logs and complete tablespace

(userspace1) online recover dropped

table 0000000000007d2a00020004 to

c:\temp\exp

Wykonanie powyższego polecenia spowodu-je przywrócenie obszaru tabel i jednoczesny eksport danych usuniętej tabeli do pliku w katalogu c:\temp\exp. Na samym końcu po-zostaje powtórnie utworzyć tabelę i załado-wać dane z tak wygenerowanego pliku (pole-

ceniem IMPORT lub LOAD). Definicja usu-niętej tabeli jest podawana razem z identy-fikatorem usuniętej tabeli w poleceniu list history.

Przy odzyskiwaniu danych z usuniętych ta-bel należy zwrócić szczególną uwagę na da-tę usunięcia tabeli. W rzeczywistym syste-mie tabela o tej samej nazwie może być wie-lokrotnie tworzona i usuwana, więc musimy określić, którą wersję tabeli będziemy chcie-li odzyskać. Przy odzyskiwaniu danych tą me-todą DB2 celowo wykonuje zrzut tekstowy zawartości usuniętej tabeli, ponieważ tabela o tej samej nazwie mogła być już powtórnie utworzona. Przytoczona metoda może wy-dawać się mało wygodna, ponieważ wyma-ga odtworzenia całego obszaru tabel. W ofer-cie IBM dostępne jest także dodatkowe narzę-dzie DB2 Recovery Expert, które pozwala od-zyskać usuniętą tabelę kilkoma kliknięciami myszki, bez konieczności odtwarzania obsza-ru tabel. Narzędzie to wykorzystuje dodat-kowe repozytorium, w którym gromadzi in-formacje o zmianach struktury bazy danych. Narzędzie pozwala także na analizę dzienni-ka bazy danych i wygenerowanie instrukcji SQL anulujących działanie określonej trans-akcji. Narzędzie jest szczególnie przydatne tam, gdzie istnieje potrzeba wykonania ręcz-nej korekty danych powstałych na skutek błę-du użytkownika.

PodsumowanieW artykule przedstawiłem szereg technik tworzenia kopii bezpieczeństwa oraz odtwa-rzania bazy danych dostępnych w DB2. Do-bór odpowiedniej techniki zależy od okre-ślonych potrzeb związanych z czasem wyko-nywania backup-u, czasem odtwarzania, roz-miaru bazy danych czy dostępnego sprzę-tu. Jeśli nie będziesz do końca pewien, któ-rą z technik zastosować, mogę poradzić: za-stosuj prostszą i łatwiejszą w implementacji. Nie sztuką jest zrobić backup, ale wielką sztu-ką jest przywrócić działającą bazę użytkowni-kom! Dlatego bardzo ważnym elementem wdrożenia jest testowanie procedur odtwa-rzania bazy po awarii. Jeśli już przygotujesz taką procedurę, poprośmy inną osobę o jej zastosowanie. Zaangażuj kogoś, kto do końca nie rozumie mechanizmów funkcjonowania backup-ów w DB2. Wytwórz presję na takiej osobie – wszystko po to, by przećwiczyć od-twarzanie bazy w warunkach zbliżonych do rzeczywistej awarii.

Rysunek 5. Konfiguracja reguł automatycznego backup-owania bazy

DB2 Recovery ExpertIBM udostępnia także dodatkowe, odpłatne narzędzie IBM DB2 Recovery Expert, które auto-matyzuje większość operacji związanych z backup-owaniem i odtwarzaniem bazy danych. Narzędzie daje także możliwość odtwarzania usuniętych obiektów bez konieczności odtwa-rzania całego obszaru tabel oraz analizę dziennika transakcji z możliwością wycofania po-szczególnych zmian na bazie danych.

Listing 10. Wyświetlenie stanu zaawansowania backup-u

db2 list utilities show detail

ID = 29

Type = BACKUP

Database Name = SAMPLE

Partition Number = 0

Description = offline db

Start Time = 2009-06-01 13:28:09.465627

State = Executing

Invocation Type = User

Throttling:

Priority = Unthrottled

Progress Monitoring:

Estimated Percentage Complete = 32

Total Work = 75595657 bytes

Completed Work = 23972655 bytes

Start Time = 2009-06-01 13:28:09.465640

ARTuR WROńSKiJest pracownikiem działu oprogramowania IBM (IBM Software Group) i specjalizuje się w zagad-nieniach baz danych DB2 oraz Informix.Kontakt z autorem: [email protected]

Page 70: Sdj Extra 35 Db2

70

Administracja

SDJ Extra 35

M enadżer obciążenia DB2 pozwa-la zarządzać priorytetami za-dań wykonywanych w bazie da-

nych. Daje możliwość wyboru, która z apli-kacji powinna otrzymać więcej zasobów, tak by spełnić oczekiwania użytkowników biz-nesowych. Czytając ten artykuł, zapoznasz się z możliwościami sterowania obciąże-niem w DB2.

Menadżer obciążenia DB2 stanowi boga-ty zestaw narzędzi administracyjnych po-zwalających na sterowanie mocą oblicze-niową dla poszczególnych aktywności w bazie danych. Dzięki temu ważniejszym, z biznesowego punktu widzenia, czyn-nościom możemy przydzielić większe za-soby i wyższy priorytet. Z drugiej strony mechanizm pozwala na spowolnienie czy wręcz zatrzymanie zbyt obciążających za-dań, bądź zawieszenie ich wykonania i od-roczenie w czasie. Pozwala to efektywniej wykorzystywać maszynę i posiadać więk-szą nad nią kontrolę. Z kolei mechanizm zbierania statystyk stanowi cenne źródło informacji o zbyt obciążających fragmen-tach systemu. Oparta na nim analiza tren-dów i najbardziej wymagających aktywno-ści pozwala na określenie sposobu wyko-rzystania zasobów serwera i może przez to stanowić system wczesnego ostrzegania (na przykład o możliwości przekroczenia jed-nego z progów).

Do wersji 9.1 zarządzanie obciążeniem DB2 było realizowane poprzez zewnętrz-ne narzędzia, takie jak IBM Query Patrol-ler i IBM DB2 Governor. Od wersji DB2 9.5 kompleksowy mechanizm (Workload Management – WLM) został wbudowany w silnik bazy danych. Mechanizm ten umoż-liwia wgląd w działanie systemu bazy da-nych oraz precyzyjną kontrolę nad zasoba-mi oraz wydajnością. Aktywności bazy da-nych można podzielić na klasy, zapewniając obsługę wielu użytkowników i aplikacji w

tym samym czasie bez konieczności wyko-rzystania dedykowanych serwerów dla naj-bardziej wymagających zadań. Dzięki temu możliwe jest utrzymanie płynności w prze-twarzaniu transakcji w operacyjnej części bazy danych przy jednoczesnym wykony-waniu długich i kompleksowych zapytań w części analitycznej (OLAP). Pozwala to zoptymalizować systemy, w których z róż-nych przyczyn (np. organizacyjnych) źró-dłem danych dla aplikacji analitycznych jest baza operacyjna bądź gdy wydzielone środowisko hurtowni danych współdzieli z nią zasoby fizyczne serwera. Tym samym rozwiązanie to może być źródłem oszczęd-ności – unikamy bowiem zakupu oddziel-nego serwera i dodatkowych licencji opro-gramowania. Co więcej, mechanizm WLM wspomaga dyscyplinę w pisaniu zapytań przez odgórną eliminację zbyt kosztow-nych konstrukcji SQL bądź ich raportowa-nie administratorowi. Wszystko to spra-wia, iż poprawne wykorzystanie mechani-zmu gwarantuje wydajniejszą pracę istot-nych części systemu informatycznego. Dla-tego warto dobrze poznać pełnię możliwo-ści Workload Management.

Scenariusz użyciaWyobraźmy sobie system bazodanowy w firmie telekomunikacyjnej. Tego typu oprogramowanie działa w systemie 24/7, gdzie wiele aktywności musi być wykony-wanych jednocześnie z zapewnieniem cią-głości dostępu i wysokiego poziomu ofero-

wanych usług. Najważniejszy, z punktu wi-dzenia biznesowego, jest szybki dostęp do bazy danych przez aplikacje obsługujące zapis informacji bilingowych. Dostęp dla aplikacji obsługujących doładowania kont pre-paid może być realizowany z mniej-szym priorytetem. Najmniejszy priorytet będą miały zaś zadania generujące rachun-ki telefoniczne. Jednocześnie baza danych musi być dostępna przez cały czas dla re-plikacji i zadań administracyjnych, takich jak wykonywanie kopii zapasowej, zadania konserwacyjne czy optymalizacyjne. Anali-tycy biznesowi mogą również zażądać moż-liwości przygotowania w tle dziennego i ob-szernego raportowania. Istnieje również ry-zyko, iż stworzone przez programistę SQL zapytania będą nadmiernie obciążały bazę danych, spowalniając w znaczący sposób jej działanie.

Zadania, za których realizację odpowie-dzialny jest serwer baz danych, można mnożyć. W celu zapewnienia ich współ-bieżnego i efektywnego wykonywania wraz z zachowaniem odpowiedniego poziomu kontroli konieczne jest użycie narzędzi do zarządzania obciążeniem (WLM). Dzięki nim można nadać aplikacjom bilingowym wysoki priorytet tak, aby jak najmniej cza-su oczekiwały na zasoby, a aplikacjom ana-lizy danych – niższy. Wraz z upływem cza-su i szybkim przyrostem danych (kilka GB tygodniowo) następuje spowolnienie sys-temu bazodanowego, co skutkuje obser-wowanym przez użytkowników wydłuże-

Mechanizm zarządzania obciążeniem w DB2

Nowoczesne systemy muszą radzić sobie z różnymi typami obciążeń. Na tej samej bazie danych uruchamiane są aplikacje transakcyjne, złożone raporty miesięczne, krótkie raporty operacyjne, a także procesy ładowania danych. Nie wszystkie aplikacje są tak samo ważne, dlaczego więc baza danych miałaby je traktować w taki sam sposób?

Rafał Stryjek, Przemysław Kantyka

Listing 1. Aktywacja mechanizmu monitorowania

ALTER SERVICE CLASS SYSDEFAULTSUBCLASS UNDER SYSDEFAULTUSERCLASS COLLECT AGGREGATE

ACTIVITY DATA BASE;

ALTER WORKLOAD SYSDEFAULTUSERWORKLOAD COLLECT ACTIVITY DATA ON COORDINATOR WITH

DETAILS;

Page 71: Sdj Extra 35 Db2

Mechanizm zarządzania obciążeniem w DB2

71www.sdjournal.org

Listing 2a. Skrypty symulujące obciążenie bazy danych dla bazy SAMPLE

Plik: work1.db2

-- wyłączenie automatycznego zatwierdzania transakcji

update command options using c off;

-- podłączenie do bazy danych SAMPLE

connect to sample;

-- wyświetlenie nazwy bieżącej aplikacji klienckiej

values(current client_applname);

--Kursory / Operacje odczytu (DML)

declare c1 cursor for select * from org;

declare c2 cursor for select * from employee;

declare c3 cursor for select * from sales;

open c1;

open c2;

open c3;

fetch c1;

fetch c2;

fetch c3;

close c1;

close c2;

close c3;

commit;

-- Operacje zapisu (DML)

insert into org values (99, 'WLM Team', 831, 'Northern',

'Canada');

insert into sales values(current date, 'GOUNOT',

'Ontario', 999);

update org set deptnumb = 999 where deptnumb = 99;

update sales set sales = 1000 where sales = 999;

delete from org where deptnumb = 999;

delete from sales where sales = 1000;

commit;

-- Zmiany struktur danych (DDL)

create table tbl1 (col1 int, col2 char(10), col3 int);

create unique index indx1 on tbl1(col1, col2);

alter table tbl1 add constraint cons1 check (col1 > 0);

-- Ładowanie danych (LOAD)

load client from data.del of del insert into tbl1;

-- Zmiany struktur danych (DDL)

drop table tbl1;

commit;

-- Zagnieżdżone zapytania

create procedure stp2

language sql

dynamic result sets 1

begin

declare callstmt varchar(2000);

declare callselect_cur cursor with return to caller for

s1;

set callstmt = 'select count(c.parm_count)

from sysibm.systables as A,

sysibm.systablespaces as B,

sysibm.sysroutines as C';

prepare s1 from callstmt;

open callselect_cur;

end;

create procedure stp3

language sql

begin

call stp2;

end;

create procedure stp4

language sql

begin

call stp3;

end;

create procedure stp5

language sql

begin

call stp4;

end;

call stp5;

commit;

drop procedure stp5;

drop procedure stp4;

drop procedure stp3;

drop procedure stp2;

commit;

connect reset;

terminate;

Plik work2.db2

-- wyłączenie automatycznego zatwierdzania transakcji

update command options using c off;

-- podłączenie do bazy danych SAMPLE

connect to sample;

-- wyświetlenie nazwy bieżącej aplikacji klienckiej

values(current client_applname);

--Kursory / Operacje odczytu (DML)

declare c1 cursor for select * from org;

declare c2 cursor for select * from employee;

declare c3 cursor for select * from inventory;

open c1;

open c2;

open c3;

fetch c1;

fetch c2;

fetch c3;

close c1;

close c2;

close c3;

commit;

-- Operacje zapisu (DML)

insert into org values (99, 'WLM Team', 831, 'Northern',

'Canada');

insert into inventory values('999-999-08', 100, 'Warehouse');

update org set deptnumb = 999 where deptnumb = 99;

update inventory set quantity = 1000 where quantity = 100;

delete from org where deptnumb = 999;

delete from inventory where quantity = 1000;

commit;

-- Zmiany struktur danych (DDL)

create table tbl2 (col1 int, col2 char(10), col3 int);

create unique index indx1 on tbl2(col1, col2);

alter table tbl2 add constraint cons1 check (col1 > 0);

-- Ładowanie danych (LOAD)

load client from data.del of del insert into tbl2;

-- Zmiany struktur danych (DDL)

drop table tbl2;

commit;

-- Zagnieżdżone zapytania

Page 72: Sdj Extra 35 Db2

72

Administracja

SDJ Extra 35

niem czasu reakcji systemu. Wówczas ad-ministrator powinien dokładnie znać przy-czyny.

Podstawowy monitoring systemuPierwszym poznawanym przez nas ele-mentem będą mechanizmy pozwalają-

ce na zbieranie istotnych informacji o sys-temie. Podczas instalacji serwera baz da-nych DB2 tworzone są obiekty umożliwia-jące (między innymi) podstawowy moni-toring, tj. domyślny menadżer obciążenia dla aktywności użytkowników – SYSTEMDEFAULTUSERWORKLOAD, oraz domyślna klasa serwisowa dla aktywności użytkowników

SYSTEMDEFAULTUSERCLASS. Poprzez mena-dżer obciążenia (ang. workloads) rozumie-my element nadzorujący aktywności, sta-nowiący zbiór informacji, który pozwala na klasyfikacje działania użytkownika (ze względu na rodzaj połączenia, charakter działań itp.) w ramach bazy danych. Klasy serwisowe (ang. service classes) są natomiast definicją sposobu obsługi tych zadań przez serwer. Szczegółowe informacje na temat tych elementów zostaną przedstawione w kolejnych częściach artykułu.

Na Listingu 1 został przedstawiony pro-ces aktywacji domyślnego monitorowania. W tym celu dla menadżera SYSTEMDEFAULTUSERWORKLOAD należy włączyć kolekcjono-wanie danych o aktywnościach, a dla kla-sy SYSTEMDEFAULTSUBCLASS kolekcjonowa-nie danych zagregowanych na poziomie pod-stawowym (klauzula COLLECT AGGREGATE ACTIVITY DATA BASE).

Celem sprawdzenia poprawności działa-nia mechanizmu monitorowania dokonamy symulacji obciążenia bazy danych poprzez wykonanie skryptów work1.db2 i work2.db2, przedstawionych na Listingu 2, wykonując polecenia:

db2 –tvf work1.db2

db2 –tvf work2.db2

Na bazie przeprowadzonych operacji me-chanizm monitorowania generuje statysty-ki i umieszcza je w pamięci. Dostęp do nich zapewniają funkcje tabelaryczne, które wy-wołujemy z poziomu zapytań SQL, np.: Li-sting 3.

Wynik działania przykładowego kodu pre-zentuje Rysunek 1. Wyświetlony rekord za-wiera informacje o zdarzeniach, które zosta-ły wykonane w ramach domyślnej klasy ser-wisowej SYSDEFAULTUSERCLASS w podklasie SYSDEFAULTSUBCLASS. Kolumna COORD_ACT_COMPLETED_TOTAL informuje o aktywno-ściach zakończonych sukcesem, a kolumna COORD_ACT_LIFETIME_AVG przedstawia śred-ni czas ich wykonania. Kolumna LAST_RESET określa natomiast moment ostatniego reseto-wania statystyk.

Etapy zarządzania obciążeniemMonitorowanie jest jednak tylko jednym z etapów procesu zarządzania obciążeniem. Pełne wykorzystanie możliwości mechani-zmu WLM wiąże się z implementacją pozo-stałych elementów.

Diagram na Rysunku 2 przedstawia eta-py mechanizmu zarządzania obciążeniem wraz z kierunkową kolejnością wykonywa-nych kroków. W przypadku gdy po przejściu

Listing 2b. Skrypty symulujące obciążenie bazy danych dla bazy SAMPLEcreate procedure stp7

language sql

dynamic result sets 1

begin

declare callstmt varchar(2000);

declare callselect_cur cursor with return to caller for s1;

set callstmt = 'select count(c.parm_count)

from sysibm.systables as A,

sysibm.systablespaces as B,

sysibm.sysroutines as C';

prepare s1 from callstmt;

open callselect_cur;

end;

create procedure stp8

language sql

begin

call stp7;

end;

create procedure stp9

language sql

begin

call stp8;

end;

create procedure stp10

language sql

begin call

stp9;

end;

call stp10;

commit;

drop procedure stp10;

drop procedure stp9;

drop procedure stp8;

drop procedure stp7;

commit;

connect reset;

terminate;

Listing 3. Zapytanie wybierające informacje o statystykach zgromadzonych w pamięci.

SELECT VARCHAR(SERVICE_SUPERCLASS_NAME, 30) AS SUPERCLASS,

VARCHAR(SERVICE_SUBCLASS_NAME, 30) AS SUBCLASS,

LAST_RESET,

COORD_ACT_COMPLETED_TOTAL,

COORD_ACT_LIFETIME_AVG

FROM TABLE(SYSPROC.WLM_GET_SERVICE_SUBCLASS_STATS('SYSDEFAULTUSERCLASS',

'SYSDEFAULTSUBCLASS',-1 )) AS T;

Rysunek 1. Statystyki domyślnego monitora dla podklasy SYSDEFAULTSUBCLASS

Page 73: Sdj Extra 35 Db2

Mechanizm zarządzania obciążeniem w DB2

73www.sdjournal.org

wszystkich kroków baza danych wciąż nie spełnia celów biznesowych, należy wykonać ponownie kroki od identyfikacji włącznie.

Etap 1. Zdefiniowanie celów biznesowychPierwszy etap polega na zdefiniowaniu ce-lów biznesowych, które chcemy osiągnąć w działaniu naszego systemu. Możemy wyróż-nić cele:

• ilościowe, np. czas odpowiedzi aplikacji podany w sekundach, wykonanie rapor-tów do określonej godziny;

• jakościowe, np. czas odpowiedzi aplika-cji powodujący u użytkowników poczu-cie komfortowej pracy.

Celami biznesowymi dla firmy telekomuni-kacyjnej z naszego scenariusza mogą być:

• zagwarantowanie szybkiego dostępu do zapisu informacji bilingowych;

• zapewnienie dostępu do doładowań kont pre-paid z mniejszym priorytetem;

• ustawienie niższego priorytetu dla czę-ści systemu generującej rachunki telefo-niczne;

• zdefiniowanie priorytetu dla raporto-wania w tle, gwarantującego wysoki po-ziom zasobów dla pozostałych aktywno-ści;

• uniemożliwienie wykonywania przez programistę SQL obciążających zapytań.

Etap 2. Identyfikacja aktywnościSam proces zarządzania aktywnościami roz-poczyna się od ich poprawnego rozpoznania. Identyfikacja aktywności stanowi bowiem etap, który pozwala serwerowi baz danych na poprawną klasyfikację procesu oraz zapew-nienie właściwego poziomu obsługi zadania.

Proces identyfikacji może być przeprowa-dzony za pomocą jednej z metod, np. na pod-stawie źródła aktywności. W praktyce każda z nich odwołuje się do jednego z dwóch ty-pów obiektów w DB2, tj. menadżerów obcią-żenia bądź klas aktywności.

Menadżerowie obciążenia (ang. workloads)Jest to klasa obiektów, które mogą zostać użyte do zidentyfikowania nadchodzą-cych zadań na bazie jego źródła. Ich atry-buty, takie jak nazwa aplikacji, identy-fikator użytkownika czy wykorzystywa-ny obiekt roli, są automatycznie zbiera-ne podczas zestawienia połączenia z ba-zą danych.

Klasy aktywności (ang. work classes)Klasy aktywności stanowią sposób klasyfi-kacji nadchodzących aktywności w bazie danych na podstawie ich typów. W Tabe-

li 1 przedstawione zostały typy aktywności określone na poziomie menadżera obciąże-nia DB2.

Dodatkowo dla instrukcji typu DML kla-sy aktywności wprowadzają możliwość uży-cia predykatów (ang. predictive elements) po-

Tabela 1. Typy aktywności bazodanowych

Typ aktywności Opis

Read (Czytanie) Zawiera wszystkie zapytania SELECT i Xquery odpowiadające za po-bieranie danych.

Write (Zapisywanie) Obejmuje wszystkie zapytania modyfikujące dane (INSERT, UPDATE, DELETE, MERGE).

Call (Wywołanie) Wskazuje wszystkie uruchomienia procedur składowanych.

DML Zawiera aktywności powstałe przez typy READ i WRITE.

DDL Określa instrukcje SQL, które tworzą lub modyfikują obiekty bazy danych.

Load (Wczytywanie) Zawiera aktywności zainicjowane przez narzędzie load.

ALL (Wszystkie) Obejmuje wszystkie typy aktywności.

Rysunek 2. Etapy mechanizmu zarządzania obciążeniem

Listing 4. Wyświetlenie statystyk dla klas serwisowych

SELECT VARCHAR(SERVICE_SUPERCLASS_NAME, 30) AS SUPERCLASS,

VARCHAR(SERVICE_SUBCLASS_NAME, 30) AS SUBCLASS,

COORD_ACT_COMPLETED_TOTAL

FROM TABLE(WLM_GET_SERVICE_SUBCLASS_STATS('','',-1)) AS T;

SUPERCLASS SUBCLASS COORD_ACT_COMPLETED_TOTAL

------------------------------ ------------------------------ --------------------

------------

SYSDEFAULTSYSTEMCLASS SYSDEFAULTSUBCLASS 0

SYSDEFAULTMAINTENANCECLASS SYSDEFAULTSUBCLASS 0

SYSDEFAULTUSERCLASS SYSDEFAULTSUBCLASS 75

Listing 5. Wyświetlenie statystyk dla menadżerów obciążenia

SELECT SUBSTR(WORKLOAD_NAME, 1, 22) AS WL_DEF_NAME,

WLO_COMPLETED_TOTAL,

CONCURRENT_WLO_ACT_TOP

FROM TABLE(WLM_GET_WORKLOAD_STATS(CAST(NULL AS VARCHAR(128)), -2)) AS WLSTATS;

WL_DEF_NAME WLO_COMPLETED_TOTAL CONCURRENT_WLO_ACT_TOP

----------------------------------- ---------------------------- ----------------

------

SYSDEFAULTUSERWORKLOAD 4 5

SYSDEFAULTADMWORKLOAD 0 0

Page 74: Sdj Extra 35 Db2

74

Administracja

SDJ Extra 35

dających szacunkowe informacje o zasobach serwera bazodanowego, które mogą być uży-te przez uruchamianą aktywność. Do predy-katów należą:

• szacunkowy koszt – wyznaczony przez kompilator zapytań SQL koszt wyrażo-ny w umownych jednostkach timeron;

• szacunkowa liczność – wyznaczona w procesie kompilacji liczba zwracanych rekordów.

Czas wykorzystać naszą wiedzę w prakty-ce. Na początku artykułu aktywowaliśmy już podstawowe monitorowanie w oparciu o domyślne obiekty mechanizmu zarządzania obciążeniem. Jak wspominaliśmy, wszyst-

kie aktywności uruchamiane były w pod-klasie SYSDEFAULTSUBCLASS domyślnej kla-sy serwisowej dla aktywności użytkownika SYSDEFAULTUSERCLASS. Sprawdźmy ponow-nie to przyporządkowanie, posługując się funkcją WLM _ GET _ SERVICE _ SUBCLASS _

STATS. Jednak tym razem w ramach para-metrów określających nazwę klasy i podkla-sy pozostawimy puste ciągi znaków (patrz Listing 4).

Otrzymany zbiór wynikowy potwier-dza wykonanie naszych zadań w kla-sie SYSDEFAULTUSERCLASS. Jednak lista klas serwisowych uległa rozszerzeniu o SYSDEFAULTSYSTEMCLASS oraz SYSDEFAULTMAINTENANCECLASS. Są to obiekty zastrze-żone dla zadań systemowych oraz mecha-

nizmów konserwacji baz danych. W przy-padku ich aktywności, wartości dla dwóch pierwszych rekordów byłyby również nie-zerowe.

W naszej konfiguracji wszystkie aktyw-ności powinny być również rozpoznawa-ne przez domyślny menadżer obciążenia – SYSDEFAULTUSERWORKLOAD. Celem weryfika-cji takiego przypisania skorzystamy w zapyta-niu SQL z funkcji WLM_GET_WORKLOAD_STATS (patrz Listing 5).

Zgodnie z oczekiwaniami nasze aktyw-ności zostały zidentyfikowane za pomo-cą domyślnego menadżera. W zbiorze wy-nikowym możemy także zobaczyć statysty-ki dla menadżera zadań administracyjnych SYSDEFAULTADMWORKLOAD.

Dokonajmy teraz rozszerzenia możliwości naszego mechanizmu zarządzania aktywno-ściami bazy danych. W tym celu definiujemy dwie nowe klasy serwisowe o nazwach odpo-wiadającym naszym skryptom symulującym obciążenie:

CREATE SERVICE CLASS work1_sc;

CREATE SERVICE CLASS work2_sc;

Następnie definiujemy menadżerów obcią-żenia dla każdego z zadań dokonujących mapowania na odpowiednie klasy serwi-sowe. W tym celu wykorzystamy specjalną zmienną rejestrową CLP, dzięki której na-zwa aplikacji jest utożsamiana z wywoły-wanym w oknie poleceń skryptem. Dodat-kowo celem zebrania dodatkowych danych w definicji menadżerów obciążenia użyje-my klauzuli COLLECT ACTIVITY DATA WITH DETAILS:

CREATE WORKLOAD work1_wl CURRENT CLIENT_

APPLNAME ('CLP

work1.db2') SERVICE

CLASS work1_sc

POSITION AT 1

COLLECT ACTIVITY

DATA WITH DETAILS;

CREATE WORKLOAD work2_wl CURRENT CLIENT_

APPLNAME ('CLP

work2.db2') SERVICE

CLASS work2_sc

POSITION AT 2

COLLECT ACTIVITY

DATA WITH DETAILS;

Ostatnim krokiem jest definicja monitorów zdarzeń dla aktywności i statystyk oraz ich aktywacja poleceniami:

CREATE EVENT MONITOR DB2ACTIVITIES FOR

ACTIVITIES WRITE TO

TABLE;

CREATE EVENT MONITOR DB2STATISTICS FOR

STATISTICS WRITE TO

TABLE;

Listing 6. Statystyki dla utworzonych klas serwisowych oraz menadżerów obciążenia

SUPERCLASS SUBCLASS COORDACTCOMP

------------------------------ ------------------------- --------------------

SYSDEFAULTSYSTEMCLASS SYSDEFAULTSUBCLASS 0

SYSDEFAULTMAINTENANCECLASS SYSDEFAULTSUBCLASS 12

SYSDEFAULTUSERCLASS SYSDEFAULTSUBCLASS 1

work1_sc SYSDEFAULTSUBCLASS 37

work2_sc SYSDEFAULTSUBCLASS 37

WL_DEF_NAME WLO_COMPLETED_TOTAL CONCURRENT_WLO_ACT_TOP

--------------------------------- ----------------------------- ------------------

----------------

work1_wl 1 5

work2_wl 1 5

SYSDEFAULTUSERWORKLOAD 1 0

SYSDEFAULTADMWORKLOAD 0 0

Listing 7. Wyświetlanie dodatkowych informacji o aktywnościach

SELECT SUBSTR(WORKLOADNAME, 1, 20) AS WL_DEF_NAME,

SUBSTR(APPL_NAME, 1, 20) AS APPL_NAME,

SUBSTR(ACTIVITY_TYPE, 1, 10) AS ACT_TYPE

FROM SYSIBM.SYSWORKLOADS, ACTIVITY_DB2ACTIVITIES

WHERE WORKLOADID = WORKLOAD_ID;

WL_DEF_NAME APPL_NAME ACT_TYPE

------------------------- ------------- ----------

... ... ...

SYSDEFAULTUSERWORKLO db2bp OTHER

SYSDEFAULTUSERWORKLO db2bp OTHER

work1_wl db2bp READ_DML

work1_wl db2bp READ_DML

work1_wl db2bp READ_DML

work1_wl db2bp READ_DML

work1_wl db2bp WRITE_DML

work1_wl db2bp WRITE_DML

work1_wl db2bp WRITE_DML

work1_wl db2bp WRITE_DML

work1_wl db2bp WRITE_DML

work1_wl db2bp WRITE_DML

work1_wl db2bp DDL

work1_wl db2bp DDL

... ... ...

Page 75: Sdj Extra 35 Db2

Mechanizm zarządzania obciążeniem w DB2

75www.sdjournal.org

SET EVENT MONITOR DB2ACTIVITIES STATE 1;

SET EVENT MONITOR DB2STATISTICS STATE 1;

Nasze obiekty są już gotowe. Aby je prze-testować, resetujemy statystyki menadże-ra połączeń, wywołując procedurę WLM _

COLLECT _ STATS:

CALL SYSPROC.WLM_COLLECT_STATS();

i uruchamiając ponownie nasze skrypty:

db2 –tvf work1.db2

db2 –tvf work2.db2

Wykorzystując zapytania SQL z Listingu 4 i Listingu 5, wyświetlamy odświeżone sta-tystyki. Rezultatem ich wykonania powin-ny być następujące zbiory rekordów (patrz Listing 6).

Wykonanie naszych skryptów zosta-ło zatem zidentyfikowane przez menadże-rów work1_wl i work2_wl oraz przekazane do odpowiadających im klas work1_sc oraz work2_sc.

Pozostaje jeszcze wyświetlić dodatkowe operacje zbierane przez menadżera obciąże-nia oraz monitory zdarzeń. Skonstruujemy w tym celu zapytanie SQL, które pokaże nam aktywności, wskazując ich typ (klasę aktyw-ności) oraz aplikację inicjalizującą oraz utoż-samiony z nimi menadżer obciążenia (patrz Listing 7).

Etap 3. ZarządzanieKolejnym i jednym z najważniejszych etapów jest aktywne zarządzanie rozpoznanymi ak-tywnościami. Możliwe jest kontrolowanie ich pracy i przydzielanie określonych prio-rytetów w zależności od aktualnych potrzeb biznesowych. W praktyce etap opiera się o de-finicję hierarchii klas aktywności (super klas i podklas), progów uruchomieniowych oraz zestawów aktywności.

Super klasy i podklasyW opisie poprzedniego etapu poznaliśmy po-jęcie klas serwisowych. Stanowią one środo-wisko uruchomieniowe, w których możemy zdefiniować zakres dostępnych zasobów dla danej aktywności, takich jak:

• priorytet agenta (ang. agent priority) – określa poziom wykorzystania proceso-ra przez agenta uruchomionych wątków w ramach danej klasy serwisowej. Prio-rytet ten zostaje przełożony do systemu operacyjnego jako wartość relatywna do poziomu określonego dla innych wąt-ków i procesów bazy danych.

• priorytet pobierania z wyprzedzeniem (ang. prefetch priority) – odpowiada za właściwe kolejkowanie żądań typu pre-fetch.

• klasa systemu operacyjnego – umożli-wia zarządzanie zasobami poprzez po-wiązanie klasy serwisowej DB2 z kla-są systemu operacyjnego posiadającego własny mechanizm zarządzania obcią-żeniem. Dla wersji DB2 9.5 obsługiwa-nym systemem operacyjnym jest IBM AIX. Wersja 9.7 rozszerza obsługę klas

systemowych o rodzinę systemów Li-nux.

• priorytet obsługi puli buforów (ang. buf-fer pool priority) – określa poziom wyko-rzystania stron puli buforów przez ak-tywności danej klasy serwisowej. Me-chanizm ten został wprowadzony w wersji DB2 9.7.

Tabela 2. Typy progów uruchomieniowych

Typ progu Opis

ACTIVITYTOTALTIME(całkowity czas aktywności)

Określa czas, jaki aktywność potrzebuje na ukończenie swojego działania (czas uruchomienia i czas oczekiwania w kolejce).

Wykorzystywany do detekcji zbyt długo trwających aktywności.

CPUTIME(czas procesora)

Definiuje maksymalny czas aktywności procesora dla danej ak-tywności.

Parametr ten został wprowadzony w wersji DB2 9.7. Pozwala na detekcje aktywności, które w znaczącym stopniu obciążają CPU.

ESTIMATEDSQLCOST(szacunkowy koszt zapy-tania)

Kontroluje aktywności odczytu i modyfikacji danych (DML – RE-AD i WRITE) o wysokim koszcie działania na podstawie informacji dostarczanych przez optymalizator DB2.

Pozwala na wprowadzenie proaktywnego działania dla aktyw-ności ze zbyt kosztownym planem zapytań.

SQLROWRETURNED(liczba zwróconych wierszy)

Definiuje maksymalną liczbę zwracanych wierszy dla zapytania SQL.

Wykorzystywany w celu uniknięcia nadmiernego obciążania ser-wera bazy danych zbyt dużą liczbą zwracanych wierszy.

SQLROWREAD(liczba przeczytanych wier-szy)

Określa maksymalną liczbę rekordów, które mogą być odczytane przez zapytanie SQL.

Parametr wprowadzony w wersji DB2 9.7. Pozwala na detekcje aktywności, które przetwarzają duże ilości rekordów.

SQLTEMPSPACE(przestrzeń zajmowana przez tabele tymczasowe)

Kontroluje wielkość zajmowaną przez obiekty tymczasowe.

Wykorzystywany do wykrywania aktywności wykorzystujących zbyt duże tabele tymczasowe.

Listing 8. Skrypt symulujący obciążenie testujący mechanizm progów uruchomieniowych

Plik: progi.db2

SELECT * FROM DEPARTMENT;

-- Dla poniższego zapytania nastąpi naruszenie progu "tr_sqlrows"

SELECT * FROM SALES;

-- Dla poniższego zapytania nastąpi naruszenie progu "tr_estcost"

SELECT COUNT(*) FROM SYSCAT.TABLES, SYSCAT.TABLES, SYSCAT.TABLES;

Listing 9. Utworzenie dwóch progów uruchomieniowych

CREATE THRESHOLD tr_estcost

FOR SERVICE CLASS threctrl_sc1 ACTIVITIES

ENFORCEMENT DATABASE

WHEN ESTIMATEDSQLCOST > 30

STOP EXECUTION;

CREATE THRESHOLD tr_sqlrows

FOR SERVICE CLASS threctrl_sc1 ACTIVITIES

ENFORCEMENT DATABASE

WHEN SQLROWSRETURNED > 30

COLLECT ACTIVITY DATA WITH DETAILS AND VALUES

STOP EXECUTION;

Page 76: Sdj Extra 35 Db2

76

Administracja

SDJ Extra 35

Aby zmienić wyżej wymienione priorytety dla aktywności uruchamianych w danej kla-sie serwisowej, przypisując im tym samym określone zasoby bazy danych, należy posłu-żyć się zapytaniem ALTER SERVICE CLASS, np.:

ALTER SERVICE CLASS klasa_sewisowa AGENT

PRIORITY -10

PREFETCH PRIORITY

MEDIUM ;

W ramach mechanizmu zarządzania obcią-żeniem DB2 administrator może zdefinio-wać dwa typy klas serwisowych: super kla-sy i podklasy. Super klasa stanowi najwyż-szy poziom przyporządkowania aktywności, natomiast podklasy są właściwymi środowi-skami wykonawczymi dla aktywności. Z te-go powodu dla każdej utworzonej super kla-sy automatycznie tworzona jest domyślna podklasa. Możliwe jest utworzenie dodatko-wych podklas serwisowych celem bardziej szczegółowego oddzielenia aktywności bądź zasobów. Jednakże niemożliwe jest tworze-nie zagnieżdżonych struktur podklas – mo-gą one być jedynie elementem super klasy.

Po nadejściu do bazy danych żądania od użytkownika serwer identyfikuje je przez wła-ściwy menadżer obciążenia. Ten przypisuje ak-

tywność do super klasy (domyślnej podklasy) bądź podklasy zdefiniowanej przez nas.

Progi (ang. thresholds)W ramach środowiska możliwe jest również zdefiniowanie progów uruchomieniowych. Pozwalają one na zdefiniowanie reakcji ser-wera baz danych, gdy aktywność przekroczy – zdefiniowaną przez próg – wartość granicz-ną. Progi te mogą być definiowane przez użyt-kownika zarówno w celu kompleksowego za-rządzania systemem, jak i wyłapywania ak-tywności nadmiernie wykorzystujących za-soby. Tabela 2 przedstawia zdefiniowane w DB2 typy progów uruchomieniowych.

Po przekroczeniu wartości granicznej dla zdefiniowanego progu uruchomieniowego mechanizm zarządzania obciążeniem podej-muje działanie określone przez użytkownika. W wersji DB2 9.7 umożliwia 4 typy reakcji:

• zebranie danych (ang. collect data) – infor-macje na temat aktywności zostaną zebra-ne w chwili przekroczenia wartości gra-nicznej progu. Samo zdarzenie jest zapisy-wane domyślnie przez dedykowany moni-tor zdarzeń (threshold violations event mo-nitor). Ten typ reakcji może stanowić uzu-pełnienie dla typów omówionych poniżej.

• zatrzymanie wykonania (ang. stop execu-tion) – aktywność zostaje zatrzymana w chwili przekroczenia zadanego progu, a do aplikacji inicjalizującej zostaje prze-kazany wyjątek.

• kontynuowanie wykonania (ang. conti-nue execution) – po przekroczeniu pro-gu aktywność nie zostaje zatrzymana. Mechanizm obciążenia zapisuje jedy-nie informacje o tym zdarzeniu na po-trzeby późniejszej analizy przez admi-nistratora.

• przeniesienie aktywności do innej klasy serwisowej (ang. remap activity) – aktyw-ność zostaje przeniesiona do innej klasy serwisowej, dla której został zdefiniowa-ny inny poziom dostępnych zasobów i progi uruchomieniowe. Mechanizm ten został dodany w wersji DB2 9.7, umoż-liwiając automatyczne przenoszenie za-dań między klasami.

Aby sprawdzić mechanizm w praktyce, zdefiniujemy nową klasę aktywności i me-nadżer obciążenia dla skryptu progi.db2 (patrz Listing 8):

CREATE SERVICE CLASS threctrl_sc1 ENABLE;

CREATE WORKLOAD threctrl_wl1

CURRENT CLIENT_APPLNAME('CLP

progi.db2')

SERVICE CLASS threctrl_sc1;

Następnie definiujemy dwa progi urucho-mieniowe: tr _ estcost i tr _ sqlrows (patrz Listing 9).

Zadaniem pierwszego będzie zatrzymanie każdej aktywności, której szacowany koszt przekroczy wartość 30. W przypadku dru-giego mechanizm zarządzania obciążeniem po zwróceniu przez bazę danych 30 wierszy zatrzyma aktywność, jednocześnie zbierając dokładne statystyki. Test uruchamiamy po-przez wywołanie skryptu poleceniem:

db2 –tvf progi.db2

Wynik jego działania został przedstawiony w Listingu 10.

Należy podkreślić, iż istnieje klasa aktyw-ności, dla której progi uruchomieniowe nie będą nigdy sprawdzane. Klasa ta jest związa-na z aktywnościami, które zostały zidentyfi-kowane przez wspomnianego na łamach te-go artykułu menadżera dla zadań administra-cyjnych (SYSDEFAULTADMWORKLOAD). W takim wypadku serwer danych zakłada, iż realizacja takiego zadania wynika ze świadomej decyzji administratora.

Zestaw aktywności (ang. work action set)Ostatnią klasą obiektów dla etapu zarządzania są zestawy aktywności. Pozwalają one na agre-

Listing 10. Komunikaty o przekroczeniu progów

… … … …

03/31/2006 LEE Manitoba 3

03/31/2006 GOUNOT Ontario-South 2

SQL4712N The threshold "TR_SQLROWS" has been exceeded. Reason code = "8".

SQLSTATE=5U026

SELECT COUNT(*) FROM SYSCAT.TABLES, SYSCAT.TABLES, SYSCAT.TABLES

SQL4712N The threshold "TR_ESTCOST" has been exceeded. Reason code = "7".

SQLSTATE=5U026

Listing 11. Klasyfikacja zapytań z wykorzystaniem szacowanego kosztu w celu zmiany priorytetów obsługi

CREATE SERVICE CLASS olap_sc UNDER work1_sc AGENT PRIORITY 0 DISABLE;

CREATE SERVICE CLASS oltp_sc UNDER work1_sc AGENT PRIORITY -6 DISABLE;

CREATE WORK CLASS SET oltp_olap_wcs(

WORK CLASS read_olap_wc WORK TYPE READ FOR TIMERONCOST FROM 10000.0 POSITION

AT 1,

WORK CLASS all_oltp_wc WORK TYPE ALL POSITION AT 2

);

CREATE WORK ACTION SET olap_oltp_mapping_wa FOR SERVICE CLASS work1_sc USING WORK

CLASS SET oltp_olap_wcs (

WORK ACTION oltp_ma ON WORK CLASS all_oltp_wc MAP ACTIVITY TO oltp_sc,

WORK ACTION "olap_ma" ON WORK CLASS read_olap_wc MAP ACTIVITY TO olap_sc )

DISABLE;

ALTER SERVICE CLASS olap_sc UNDER work1_sc ENABLE;

ALTER SERVICE CLASS oltp_sc UNDER work1_sc ENABLE;

ALTER WORK ACTION SET olap_oltp_mapping_wa ENABLE;

Page 77: Sdj Extra 35 Db2

www.sdjournal.org

gowanie aktywności, które są identyfikowane przez jeden lub więcej typów (patrz: Klasy ak-tywności). Zbiory takie mogą zostać zdefinio-wane na poziomie bazy danych, a także przy-pisane w ramach klas serwisowych.

Podejście takie pozwala na przypisanie ak-tywnościom listy zadań, które będą wywoły-wane w zależności od – określonych w ich de-finicji – klas aktywności i zakresu wykorzy-stywanych zasobów. W szczególności do za-dań tych należy mapowanie danej aktywno-ści do podklasy serwisowej. Dzięki temu mo-żemy wyróżnić na przykład operację odczytu, którego koszt przekroczy 10 000 jednostek ti-meron (patrz Listing 11).

Przykład taki może być odzwierciedlo-ny przez kosztowne zapytania analityczne (OLAP) uruchamiane w tle z mniejszym priorytetem.

Etap 4. MonitorowanieMonitorowanie pozwala na weryfikację pro-cesu pod kątem założonych celów bizneso-wych oraz umożliwia analizę trendów ak-tywności w systemie. Jak zostało to określo-ne na początku artykułu, monitorowanie sta-nowi zazwyczaj pierwszy krok pracy z me-chanizmem WLM ze względu na koniecz-ność zdiagnozowania systemu.

Posługując się dostępnymi w DB2 funkcja-mi tabelarycznymi, użytkownik uzyskuje do-stęp w czasie rzeczywistym do danych opera-cyjnych, takich jak lista klas serwisowych, za-wierająca informacje o liczbie uruchomio-nych w nich aktywności i średnim czasie wy-konania (patrz Rysunek 1). Wiedza ta może zostać rozszerzona, przy wykorzystaniu mo-nitorów zdarzeń (ang. event monitors), o szcze-gółowe informacje oraz statystyki dla zagrego-wanych wartości. Na zbieranie i analizę tych ostatnich warto zwrócić uwagę, planując stra-tegię monitorowania bazy danych. Proces ta-ki jest bowiem mniej obciążający dla serwera niż zbieranie informacji o pojedynczym zda-rzeniu, a uzyskiwane wyniki dają dobry, ogól-ny obraz wszystkich aktywności.

W ramach etapu monitorowania możemy wyróżnić dwa typy:

• monitorowanie w czasie rzeczywistym – oparte o statystyki reprezentujące ak-tualne aktywności w bazie danych. Ich analiza przeprowadzana jest z wykorzy-staniem jednej z pięciu funkcji tabela-rycznych zwracających dane dla pozio-mu: super klas, podklas, menadżerów obciążenia, zestawu aktywności bądź progów uruchomieniowych.

• monitorowanie w oparciu o dane historycz-ne – mechanizm WLM pozwala również na zbieranie informacji, które mogą być użyte w przyszłości w celu analiz histo-rycznych. Dla tworzenia tego typu infor-macji wyróżniamy 3 typy monitorów:

• monitor aktywności (ang. activity event monitor) – zbiera informacje o indywidualnych aktywnościach po-grupowanych ze względu na obiek-ty mechanizmu WLM.

• monitor dla przekroczonych wartości granicznych progów (ang. threshold violations event monitor) – zbierają-cy informacje o aktywnościach prze-kraczających zadany próg.

• monitor statystyk (ang. statistic event mo-nitor) – zbiera dane w postaci zagre-gowanej, np. histogramy dla czasów trwania i uruchomienia aktywności.

Oba typy monitorowania zostały przez nas wykorzystane podczas omawiania kolejnych etapów zarządzania obciążeniem.

PodsumowanieIdeą artykułu było zapoznanie czytelnika z podstawami mechanizmu WLM służące-go do zarządzania obciążeniem celem lep-szego wykorzystania zasobów i tym samym usprawnienia pracy systemu. Pokazano moż-liwości w zakresie najważniejszych etapów powiązanych z utrzymywaniem obciążenia, tj. monitorowania, zarządzania, definicji oraz identyfikacji aktywności. Serwer danych DB2 dostarcza tu wachlarz możliwości, któ-re zostały zobrazowane za pomocą skryptów odzwierciedlających rzeczywiste wykorzysta-nie. Wykorzystanie zdobytej wiedzy pozwoli lepiej i wydajniej zarządzać systemami klasy enterprise, w których wymagania jakościowe są niezwykle istotne. Warto jeszcze raz pod-kreślić, iż wykorzystanie mechanizmu WLM może wiązać się z uzyskaniem finansowych oszczędności. Jednak należy uważać, by nie potraktować go jako panaceum na wszystkie problemy bazodanowe, w szczególności nie-optymalnie napisane zapytania SQL.

Rafał STRyjEKRafał Stryjek - doktorant Politechniki Łódzkiej. Certyfikowany Trener i Konsultant baz danych Oracle wspierający pracowników największych firm i instytucji w kraju. Prowadzi zaawansowane szkolenia w firmie Altkom Akademia S.A. Wcze-śniej współpracował z firmą IBM Polska z wyko-rzystaniem baz danych DB2 oraz Oracle.e-mail: [email protected]

PRZEMySław KanTyKaPrzemysław Kantyka - certyfikowany specja-lista bazodanowy z wieloletnim doświadcze-niem w pracy z bazami danych (głównie Orac-le) na potrzeby sektora finansowego. Jako Kon-sultant i Trener wyszkolił dziesiątki osób z zakre-su zaawansowanych technologii bazodanowych. Współpracował między innymi z firmami takimi jak Comarch i Altkom Akademia S.A.e-mail: [email protected]

Page 78: Sdj Extra 35 Db2

78

Administracja

SDJ Extra 35

Dzisiaj o bazach danych Open Sour-ce można powiedzieć, że osiągnę-ły już odpowiedni poziom dojrza-

łości, by stosować je w komercyjnych syste-mach. Bazy danych stały się po prostu po-wszechnym produktem, takim jak telewi-zor czy samochód. W języku angielskim naj-łatwiej określić je jednym słowem – commo-dity. Czy oznacza to, że dostępne na rynku bazy danych nie różnią się od siebie i oferu-ją podobne możliwości? Takie samo pytanie można postawić w stosunku do innych pro-duktów commodity. Czy dziś ma znaczenie, jaki kupujemy samochód? Oczywiście, że ma! Wszystko jednak zależy od wymagań, jakie stawiamy. Jeśli ktoś potrzebuje samo-chodu, by raz w tygodniu odwiedzić rodzi-nę, która mieszka kilka kilometrów od na-szego domu, wtedy naprawdę nie ma zna-czenia, czym będziemy się przemieszczać. Jeśli jednak zaczniemy intensywnie używać samochodu, wtedy nie bez znaczenia będzie dla nas zużycie paliwa, bezpieczeństwo, nie-zawodność czy komfort. Jeśli samochód sta-nie się dla nas miejscem pracy – wtedy już na pewno zaczniemy wymagać więcej. Po-dobnie jest i z bazami danych. Jeśli naszym jedynym wymaganiem będzie przechowa-nie kilku GB danych, wtedy wszystkie bazy będą tak samo dobre. Różnicę będzie moż-na zauważyć przy bazie, która np. zarządza kilkoma terabajtami danych.

W tym artykule opiszę mechanizmy kompresji danych dostępne w DB2, któ-re stosowane są dla dużych baz danych. Oczywiście duża baza jest bardzo subiek-tywnym określeniem. Dla mnie duża ba-za to taka, która wymaga podjęcia specjal-nych akcji, pozwalających na przełama-nie pewnych ograniczeń będących natu-ralną konsekwencją rozmiaru bazy. Do ta-kich ograniczeń mogę zaliczyć maksymal-ny czas, który mogę poświęcić na wykona-nie backup-u bazy danych, dostępne miej-

sce na dyskach czy środki finansowe, któ-re mogę przeznaczyć na zakup i utrzyma-nie systemów dyskowych.

Dla przeciętnego użytkownika kompu-tera osobistego kompresja nie zawsze ko-jarzy się z czymś pozytywnym, najczę-ściej z oczekiwaniem na rozpakowanie plików. W wielu dziedzinach to właśnie inteligentne algorytmy kompresji wyzna-czają kierunki rozwoju. Przykładem mo-gą być choćby algorytmy kompresji wy-korzystane do strumieniowego przeka-zu obrazu wideo wysokiej rozdzielczości (H.264/AVC), stosowane przez wiele cy-frowych platform satelitarnych. Nawet poza informatyką można podać przykła-dy wykorzystania kompresji, przynoszą-ce odpowiednie korzyści. Producentom soków (np. pomarańczowych) opłaca się zagęścić sok w miejscu, w którym jest on produkowany, przetransportować zagęsz-czony sok do docelowego kraju, a następ-nie uzupełnić sok wodą w odpowiednich proporcjach. Taka operacja wymaga do-datkowej pracy, jednak w ogólnym bilan-sie obniża koszty i przyspiesza proces wy-tworzenia soku.

Zanim omówię zasady działania kom-presji w DB2 chciałbym zwrócić uwagę na trendy rozwoju sprzętu komputero-wego w ostatnich latach. Prędkość pro-cesorów z roku na rok systematycznie się zwiększa. Pojemność dysków także się zwiększa. Dziś dostępne są już dyski 600 GB stosowane w macierzach z interfej-sem Fiber Channel (dyski SATA II oferu-ją jeszcze większe pojemności, jednak nie zawsze nadają się do rozwiązań pod bazy danych). Natomiast prędkość dysków po-zostaje bez zmian – wynika to z fizycz-nych ograniczeń prędkości obrotowej ta-lerza dyskowego, wynoszącego maksy-malnie 15 tysięcy obrotów na minutę. Przygotowując architekturę sprzętu pod

bazę danych, bardzo kusi skorzystanie z jak największych dysków, ponieważ większe dyski przekładają się na mniejszą cenę sys-temu dyskowego, jednak wybór większych dysków oznacza mniejszą wydajność. Bazy danych czytają małymi blokami, a wydaj-ność każdego z dysków określona jest przez liczbę operacji losowego odczytu/zapisu na sekundę. Zrównoważona wydajność opera-cji I/O bezpośrednio zależy od liczby dys-ków, a nie przestrzeni dyskowej! Tak jak po-kazałem na Rysunku 1, by zapewnić 600 GB przestrzeni dyskowej, możemy skorzy-stać z czterech dysków po 150 GB bądź z dwóch po 300 GB. Zastosowanie więk-szych dysków zapewne będzie tańszym roz-wiązaniem, jednak pociągnie za sobą dwa razy mniejszą wydajność mierzoną w licz-bie operacji I/O na sekundę.

Specjaliści przygotowujący sprzęt pod testy wydajnościowe przetwarzania analitycznego TPC-H (www.tpc.org) doskonale rozumieją konieczność użycia bardzo dużej liczby dys-ków. Najlepszy wynik testu TPC-H dla bazy danych o rozmiarze 10 TB, zrealizowany na DB2 9.5 został osiągnięty przy wykorzysta-niu macierzy dyskowej, której powierzchnia dyskowa była 11 razy większa niż rozmiar bazy danych! 110 TB to dużo? Drugi w ko-lejności wynik zrealizowany na bazie Oracle 11g wykorzystywał macierz dyskową 44 razy większą niż rozmiar bazy danych (440 TB)! Testy wydajnościowe TPC-H rządzą się swo-imi prawami, które wszystko podporządko-wują osiągnięciu najlepszego wyniku, i czasa-mi trudno je bezpośrednio odnieść do rzeczy-wistych rozwiązań. Podobnie jak nikogo nie dziwi fakt, że wykonanie kierownicy samo-chodu Formuły 1 kosztuje tyle samo co wy-produkowanie nowego, popularnego samo-chodu osobowego. Przytoczyłem powyższe porównania, by zasygnalizować konieczność optymalizacji systemu dyskowego, np. przy wykorzystaniu kompresji.

Kompresja w DB2

W ostatnich latach rynek oprogramowania do zarządzania bazami danych zupełnie zmienił swoje oblicze. Jeszcze kilka, kilkanaście lat temu bazy danych należały do elitarnego oprogramowania i projektanci systemów informatycznych byli wręcz skazani na korzystanie z komercyjnych rozwiązań.

Optymalizacja systemu dyskowego

Page 79: Sdj Extra 35 Db2

Kompresja w DB2

79www.sdjournal.org

Jak działa kompresja w DB2?DB2 implementuje kilka różnych algoryt-mów zależnie od tego, czy kompresowa-ne są rekordy, indeksy czy np. backup bazy danych. Dla kompresji tabel istotą jest zu-pełnie inny format zapisu rekordu na stro-nie. By zrozumieć, jak działa kompresja, przypomnę podstawy organizacji danych w DB2. Na dyskach dane zorganizowane są w strony, które stanowią podstawową jednost-kę operacji wejścia/wyjścia. Jeśli chcemy za-pytaniem SQL odczytać pojedynczy rekord, wtedy minimalną porcją danych, jaka zosta-nie odczytana z dysku, jest strona. Strona zostanie umieszczona w puli buforów (ca-che), która jest współdzielonym obszarem pamięci RAM wykorzystywanym do obsłu-gi wszystkich aplikacji. Jeśli inny użytkow-nik (bądź ten sam) będzie chciał odczytać rekord ze strony, która znajduje się już w puli buforów, wtedy odczyt będzie zrealizo-wany bezpośrednio z pamięci bez koniecz-ności fizycznego odwoływania się do dys-ku. Rozmiar strony w DB2 jest definiowal-ny (4 KB, 8 KB, 16 KB lub 32 KB), a na jed-nej stronie zwykle mieści się typowo od kil-ku do kilkuset rekordów (zależnie od szero-kości rekordu).

Działanie kompresji polega na zreduko-waniu szerokości rekordu, tak by na stro-nie można było upakować większą liczbę re-kordów. Jeśli często powtarzający się w ta-beli wzorzec występuje w rekordzie, wtedy podmieniany jest na odpowiedni 12-bitowy znacznik, którego definicja przechowywana jest w specjalnym słowniku systemowym. Istotą algorytmu kompresji DB2 jest przygo-towanie odpowiedniego słownika wzorców. W DB2 słownik wzorców budowany jest na podstawie zawartości całej tabeli (bądź wy-różnionego fragmentu tabeli). Operacja ta-ka jest stosunkowo czasochłonna (wyma-ga przeskanowania dużej liczby rekordów) i może znacznie obciążyć system, dlatego też decyzja o zbudowaniu słownika najczę-ściej podejmowana jest świadomie przez ad-ministratora bazy danych. W momencie bu-dowania słownika, DB2 ocenia całe wiersze, szukając podobnych fragmentów lub powtó-rzonych wpisów danych i to nie tylko dla wybranych pól czy części pól, ale całych cią-gów, rozciągających poprzez wiele kolumn w wierszu. Zakładając, że słownik jest już zbudowany, wtedy w momencie wstawie-nia bądź aktualizacji rekordu, DB2 przepro-wadza analizę rekordu pod kątem zgodności wzorców i proponuje skrócony format zapi-su rekordu. Jeśli w słowniku nie znajdują się wzorce, które pasują do wstawianego (mo-dyfikowanego) rekordu, wtedy rekord wpro-wadzany jest w oryginalnej postaci, tak jak dla tabel, które nie wykorzystują kompre-sji. Słownik nie jest aktualizowany w trybie online, ponieważ wiązałoby się to ze zbyt

dużym nakładem pracy. Narzut związany z obsługą skompresowanej tabeli jest stosun-kowo niewielki. Wykorzystujemy dodatko-we cykle procesora głównie przy modyfika-cji danych, by odszukać w słowniku wzorce. Odczyty danych praktycznie nie pociągają za sobą dodatkowego narzutu. W momencie ewaluacji wartości rekordu bądź wysyłania strumienia danych do aplikacji, DB2 dyna-micznie podmienia zawartość znaczników. Algorytmy są tak dopracowane, że rozmiar słownika wzorców praktycznie nie przekra-cza 150 KB, niezależnie od rozmiaru tabe-li. Słownik przechowywany jest zawsze w pamięci podręcznej bazy danych, dlatego też dostęp do niego jest bardzo szybki.

Główną korzyścią ze stosowania kompre-sji jest zmniejszenie szerokości rekordów. Na tej samej stronie zostanie zapisanych du-żo więcej rekordów. Współczynniki kompre-sji silnie zależą od samych danych. Na podsta-wie obserwacji rzeczywistych systemów (np. SAP ERP) można powiedzieć, że przeciętny współczynnik kompresji wynosi ok. 60-70%,

tzn. tabela o rozmiarze 100 GB po kompresji powinna zajmować 30-40 GB. Zmniejszenie rozmiaru tabeli nie jest jedyną korzyścią wy-nikającą z kompresji. Ponieważ format stron przechowywanych w pulach buforów jest ta-ki sam jak na dyskach, w tej samej ilości pa-mięci RAM serwera bazy danych znajdować się będzie więcej rekordów. Dzięki kompre-sji wirtualnie zwiększamy dostępną dla bazy pamięć RAM. Kolejną korzyścią jest zwięk-szenie przepustowości operacji I/O. Jak już wspomniałem, baza danych czyta blokami (stronami). Z jednym odczytem, proporcjo-nalnie do współczynnika kompresji, czytamy większą liczbę rekordów – wirtualnie wzra-sta przepustowość systemu dyskowego (patrz Rysunek 2). Kompresja pociąga za sobą nie-znaczny wzrost utylizacji procesora – takie zachowanie wpisuje się w omówiony wcze-śniej trend wzrostu mocy procesorów.

Włączenie kompresjiDo zademonstrowania poszczególnych kro-ków związanych z włączeniem kompresji wy-

Rysunek 1. Zastosowanie dwa razy większych dysków zmniejszy wydajność mierzoną w liczbie operacji I/O na sekundę

Listing 1. Sprawdzenie miejsca zajmowanego przez tabele bazy danych

select char(tabname, 10) as table, data_object_l_size as "DATA SIZE [KB]",

index_object_l_size as "INDEX SIZE [KB]" from

sysibmadm.admintabinfo where tabschema = 'TPCH' order by

data_object_l_size desc;

TABLE DATA SIZE [KB] INDEX SIZE [KB]

---------- -------------------- --------------------

LINEITEM 833024 144384

ORDERS 183808 43264

PARTSUPP 127872 45568

PART 30720 3456

CUSTOMER 27904 3968

SUPPLIER 1792 512

NATION 256 256

REGION 256 256

Page 80: Sdj Extra 35 Db2

80

Administracja

SDJ Extra 35

korzystałem tabele oraz dane przygotowa-ne dla testu TPC-H, zgodnie ze specyfikacją opublikowaną na stronie www.tpc.org. Tabele załadowałem plikami tekstowymi o rozmia-rze 1 GB, które wygenerowałem programem dbgen, dostarczanym razem z testem TPC-H. Włączenie kompresji ma sens dla tych tabel, które zajmują dużo miejsca, oraz dla których kompresja przyniesie odpowiednio duże ko-rzyści (współczynniki kompresji będą odpo-wiednio wysokie), dlatego też warto wyko-nać wstępną analizę potencjalnych zysków z włączenia kompresji. DB2 dostarcza szereg odpowiednich widoków oraz funkcji admi-nistracyjnych, które pozwalają na wyliczenie miejsca zajmowanego przez tabele, indeksy, obiekty XML oraz obiekty BLOB. Dostępne

są także funkcje, które pozwalają oszacować współczynniki kompresji dla danych oraz in-deksów. Po załadowaniu danych odpowied-nim zapytaniem pokazanym na Listingu 1 sprawdziłem, ile miejsca zajmują poszcze-gólne tabele oraz ich indeksy. Do wygenero-wania raportu wykorzystałem widok admini-stracyjny ADMINTABINFO ze schematu SY-SIBMADM. Raport posortowałem malejąco od największych tabel do najmniejszych. Do dalszej analizy wybrałem trzy największe ta-bele, które zajmowały 95% rozmiaru wszyst-kich tabel testu TPC-H (1.4 GB): LINEITEM – 950 MB (dane + indeksy), ORDERS – 220 MB oraz PARTSUPP – 169 MB.

Następnie dla każdej z trzech wybranych tabel uruchomiłem funkcję tabelaryczną

ADMIN_GET_TAB_COMPRESS_INFO_V97 z argu-mentem ESTIMATE, by wstępnie oszacować współczynniki kompresji. Funkcję urucho-miłem niezależnie dla każdej z tabel, poda-jąc nazwę tabeli jako drugi argument funk-cji, tak jak pokazałem to na Listingu 2. Oczy-wiście mogłem wywołać funkcję tak, by wyli-czyć współczynniki dla wszystkich tabel (pu-sty ciąg znaków zamiast nazwy tabeli) i w wa-runku WHERE zapytania wybrać interesujące mnie rekordy. Takie podejście byłoby dużo kosztowniejsze dla bardzo dużej bazy danych. W moim środowisku (notebook Lenovo T61, Intel Core2 duo, 2 GHz) wykonanie estyma-cji dla największej tabeli LINEITEM zajęło 26 sekund, więc różnica byłaby niezauważalna – w ogólnym przypadku lepiej wykonywać takie analizy tabela po tabeli, przede wszyst-kim ze względu na większą kontrolę. Szaco-wane współczynniki kompresji przedstawi-łem na Listingu 3. Jak widać, rozmiar słow-nika kompresji jest podobny dla każdej z ta-bel (ok. 40 KB), mimo iż tabele znacznie róż-niły się rozmiarem.

Kompresję włączyłem instrukcją ALTER

TABLE oraz ALTER INDEX, dla każdej z trzech wy-branych tabel oraz indeksów tych tabel. Poniżej przedstawiłem przykładową instrukcję dla tabe-li LINEITEM. Nazwy indeksów dla tabeli pobra-łem poleceniem DESCRIBE INDEXES FOR TABLE TPCH.LINEITEM:

alter table tpch.lineitem compress yes;

alter index tpch.l_okln compress yes;

Wykonanie instrukcji ALTER .. COMPRESS

zmienia definicję tabeli, natomiast nie two-rzy słownika oraz nie wykonuje przebudo-wania istniejących tabel do nowego, skróco-nego formatu. Taką przebudowę połączoną z tworzeniem słownika można wykonać po-leceniem REORG, tak jak pokazałem to poni-żej. Wykonanie przebudowy największej ta-beli LINEITEM zajęło w moim środowisku 4 minuty:

reorg table tpch.lineitem use tempspace1

reorg indexes all for table tpch.lineitem

Po przebudowaniu wszystkich trzech ta-bel (LINEITEM, ORDERS, PARTSUPP) sprawdzi-łem, ile miejsca teraz zajmują tabele. Wynik został przedstawiony na Listingu 4. Jak wi-dać, rozmiar tabel znacznie się zmniejszył. Łączny rozmiar bazy danych zmniejszył się

Listing 2. Oszacowanie współczynnika kompresji dla wybranej tabeli (TPCH.LINEITEM)

select char(tabname,10) as table, compress_dict_size as "DICTIONARY SIZE [B]",

pages_saved_percent as "PAGES SAVED [%]" from table(sysproc.

admin_get_tab_compress_info_v97 ('TPCH','LINEITEM',

'ESTIMATE') ) as t where object_type = 'DATA' ;

Listing 3. Wyniki wstępnego oszacowania współczynników kompresji (dane bez indeksów)

TABLE DICTIONARY SIZE [B] PAGES SAVED [%]

---------- -------------------- ---------------

LINEITEM 46208 60

ORDERS 40192 61

PARTSUPP 43648 65

Tabela 1. Przykładowe dwa rekordy tabeli XML.PARTSUPP

ID PARTSUPP

1 <row><PS _ PARTKEY>1</PS _ PARTKEY><PS _ SUPPKEY>2</PS _ SUPPKEY><PS _ AVAILQTY>3325</PS _ AVAILQTY><PS _SUPPLYCOST>7.7164E2</PS _ SUPPLYCOST><PS _ COMMENT>requests after the carefully ironic ideas cajole alongside of the enticingly special accounts. fluffily regular deposits haggle about the blithely ironic deposits. regular requests sleep c</PS _ COMMENT></row>

2 <row><PS _ PARTKEY>1</PS _ PARTKEY><PS _ SUPPKEY>2502</PS _ SUPPKEY><PS _ AVAILQTY>8076</PS _ AVAILQTY><PS _SUPPLYCOST>9.9349E2</PS _ SUPPLYCOST><PS _ COMMENT>careful pinto beans wake slyly furiously silent pinto beans. accounts wake pendi</PS _ COMMENT></row>

Rysunek 2. Zastosowanie kompresji pozwala zwiększyć wydajność systemu dyskowego i wirtualnie zwiększyć bufor bazy danych

Page 81: Sdj Extra 35 Db2

Kompresja w DB2

81www.sdjournal.org

z 1.40 GB do 0.66 GB. Oczywiście rozmiar plików na dysku pozostał ten sam, a jedy-nie wewnętrzne wykorzystanie przestrzeni zostało zredukowane. By zredukować także rozmiar plików na dysku, można wykorzy-stać instrukcję ALTER TABLESPACE REDUCE, tak jak poniżej:

alter tablespace userspace1 reduce max

Wykonanie powyższej instrukcji spowoduje zmniejszenie rozmiaru kontenerów (plików) obszaru tabel USERSPACE1 poprzez usunięcie pustych ekstent-ów, nieprzydzielonych do żad-nego z obiektów. Porównałem rozmiary konte-nera tego obszaru tabel (plik C0000000.LRG ) sprzed i po wykonaniu tej instrukcji i wy-nik odpowiada uzyskanemu współczynnikowi kompresji (patrz Listing 5).

Automatyczne generowanie słownikaPrzedstawione powyżej podejście kompresji istniejących danych wymaga reorganizacji ta-beli w trybie offline, co oznacza, że tabela jest niedostępna dla użytkowników na czas prze-budowy. Do skompresowania tabeli podczas normalnej pracy użytkowników można wy-korzystać specjalną procedurę składowaną ADMIN_MOVE_TABLE, która pozwala przenosić tabele w trybie online pomiędzy różnymi ob-szarami tabel. W trakcie przenoszenia istnie-je możliwość zmiany atrybutów tabeli, w tym tych związanych z kompresją.

Innym mechanizmem włączenia kom-presji, nieograniczającym dostępności tabe-li, jest mechanizm automatycznego genero-wania słownika kompresji – ADC (Automa-tic Dictionary Creation). Wykorzystanie te-go mechanizmu ogranicza się do utworze-nia tabeli z włączonym atrybutem kompre-sji. Podczas wstawiania danych do tabeli, DB2 na bieżąco aktualizuje definicję słow-nika kompresji. Rekordy wstawiane są jed-nak w nieskompresowanym formacie aż do momentu, kiedy DB2 uzna, że jakość wzor-ców w słowniku jest na tyle dobra, że moż-na zatwierdzić jego definicję. Od tego mo-mentu słownik nie będzie już aktualizo-wany, a nowo wstawiane dane będą zapi-sywane już w skompresowanym formacie. Moment, w którym DB2 zaczyna zapisy-wać rekordy w nowym formacie, jest au-tomatycznie wybierany przez DB2. W ce-lu przetestowania mechanizmu ADC po-wtórnie utworzyłem tabele oraz indek-sy bazy TPCH, dodając do definicji tabel klauzulę COMPRESS YES, a następnie załado-wałem dane narzędziem IMPORT. Narzędzie IMPORT ładuje dane przy wykorzystaniu in-strukcji SQL INSERT i dobrze oddaje spo-sób, w jaki interaktywne aplikacje wprowa-dzają dane. Rozmiar tabel tylko nieznacz-nie różnił się od kompresji metodą offline

(patrz Listing 6 i porównaj go z Listingiem 4). Łączny rozmiar tabel skompresowanych automatycznie wyniósł 0.68 GB – dla po-równania, kompresując tabele metodą of-fline, rozmiar wynosił 0.66 GB. W niektó-rych przypadkach metoda automatyczne-go generowania słownika może dać gorsze współczynniki kompresji, nie wymaga jed-nak reorganizacji i rozmiar plików na dysku jest zbliżony do faktycznego rozmiaru (nie wymaga pomniejszania).

XML, BLOBDB2 9.7 pozwala także kompresować do-kumenty XML oraz BLOB-y. Standardo-wo obiekty typu BLOB przechowywa-ne są w wydzielonej przestrzeni. Podob-nie też dokumenty XML przechowywane są nie na stronach z danymi podstawowy-mi, a w osobnym obszarze hierarchicznym. DB2 umożliwia umieszczenie tego rodzaju obiektów na stronach razem z danymi pod-stawowymi. Taka metoda przechowywania obiektów określana jest jako inlineing. Po-

niżej przedstawiłem definicję tabeli, któ-ra przechowuje dokumenty XML na stro-nie razem z podstawowymi danymi (klau-zula INLINE):

create tablespace xmlspace;

create table xml.partsupp

(id int, partsupp xml inline length 1000)

in xmlspace compress yes;

Przy takiej definicji tabeli dokumenty XML nieprzekraczające 1000 bajtów (klauzula LENGTH 1000) będą przechowywane na stro-nach razem z podstawowymi danych (po-le ID). Jeśli rozmiar dokumentu przekroczy zdefiniowany próg 1000 bajtów, wtedy za-wartość pola PARTSUPP zostanie automatycz-nie przeniesiona do hierarchicznego repozy-torium. Przechowywanie mniejszych obiek-tów XML razem z pozostałymi danymi po-zwala skorzystać z omówionych wcześniej al-gorytmów kompresji. By zademonstrować kompresję dokumentów XML, załaduję na-rzędziem LOAD tabelę XML.PARTSUPP danymi

Listing 4. Sprawdzenie miejsca zajmowanego przez tabele po kompresji

SELECT CHAR(TABNAME, 10) AS TABLE, DATA_OBJECT_L_SIZE AS "DATA SIZE [KB]",

INDEX_OBJECT_L_SIZE AS "INDEX SIZE [KB]" FROM

SYSIBMADM.ADMINTABINFO WHERE TABSCHEMA = 'TPCH' ORDER BY

DATA_OBJECT_L_SIZE DESC;

TABLE DATA SIZE [KB] INDEX SIZE [KB]

---------- -------------------- --------------------

LINEITEM 324864 115072

ORDERS 71040 37760

PARTSUPP 43136 35584

PART 30720 3456

CUSTOMER 27904 3968

SUPPLIER 1792 512

NATION 256 256

REGION 256 256

Listing 5. Porównanie rozmiaru plików bazy na dysku sprzed i po kompresji

Directory of C:\DB2\NODE0000\TPCH\T0000002

2009-07-19 22:54 1 509 949 440 C0000000.LRG

2009-07-19 22:35 0 SQLCRT.FLG

2 File(s) 1 509 949 440 bytes

2009-07-20 02:35 714 080 256 C0000000.LRG

2009-07-19 22:35 0 SQLCRT.FLG

2 File(s) 714 080 256 bytes

Tabela 2. Rozmiar i czas wykonania backup-ów

Rozmiarbackup-u

Czas Kompresja tabel Kompresja backup-u

1536 MB 5m 25s nie nie

532 MB 2m 02s nie tak

608 MB 2m 09s tak nie

456 MB 1m 36s tak tak

Page 82: Sdj Extra 35 Db2

82

Administracja

SDJ Extra 35

z tabeli TPCH.PARTSUPP. Narzędzie LOAD ładu-je dane w sposób blokowy (nietransakcyjny) i podobnie jak narzędzie IMPORT obsługuje au-tomatyczną kompresję. Polecenia ładujące ta-belę przedstawiłem na Listingu 7. W pierw-szej linii tworzę kursor oparty na zapytaniu przetwarzającym wszystkie rekordy z tabe-li TPCH.PARTSUPP, natomiast w drugiej ładu-ję docelową tabelę, podając nazwę kursora K1 jako źródło ładowanych danych. Zapytanie pobierające wiersze do załadowania wykorzy-stuje funkcję XMLROW, która buduje dokument XML zawierający wartości wszystkich pól ta-beli ubranych w znaczniki będące nazwami kolumn. W Tabeli 1 pokazałem przykładowe dwa rekordy z XML.PARTSUPP.

Podobne ćwiczenie wykonałem dla tabeli o identycznej strukturze co XML.PARTSUPP, lecz nieskompresowanej. Nieskompresowana tabela XML zajmowała 357 MB, natomiast utworzo-na z klauzulą COMPRESS YES – 118 MB, co da-ło 70% współczynnik kompresji. Warto jeszcze wspomnieć, że dla obiektów XML DB2 budu-je dodatkowy (drugi) słownik, ponieważ wzor-ce dokumentów XML mogą znacząco różnić się od wzorców występujących w danych pod-stawowych.

Tabele tymczasoweWarto wspomnieć, że DB2 posiada także wbu-dowane mechanizmy kompresji tabel tymcza-sowych. Kompresja dla takich tabel stosowana jest przez DB2 automatycznie. Baza samoczyn-nie sprawdza, czy wykorzystanie kompresji dla danej tabeli tymczasowej przyniesie korzyści, i podejmuje decyzję o włączeniu bądź wyłącze-niu kompresji.

BackupDB2 udostępnia także mechanizm kompresji backup-ów, który na bieżąco pakuje bloki prze-syłane do obrazu backup-u. By wykonać skom-presowany backup, wystarczy dodać klauzulę COMPRESS, tak jak w przykładzie poniżej:

db2 backup db tpch compress

Opcjonalnie można także podać nazwę bi-blioteki, która ma być wykorzystana do kompresowania bloków danych (jeśli ma być inna niż ta dostarczona z instalacją DB2). Przeprowadziłem kilka testów, by zo-rientować się, jak kompresja wpływa na roz-miar plików zawierających obraz backup-u oraz czas jego wykonania. Najpierw wyko-nałem backup dla bazy nieskompresowanej z wyłączoną opcją kompresji backup-u. Na-stępnie jeszcze raz wykonałem backup tej samej bazy, ale z włączoną opcją kompre-sji backup-u. Obydwa pomiary powtórzy-łem dla bazy danych, która zawierała już skompresowane dane. W Tabeli 2 przed-stawiłem wyniki doświadczenia. Jak wi-dać, najdłużej wykonywał się backup bazy, która nie zawierała skompresowanych tabel oraz dla której nie włączono kompresji bac-kup-u. Na pierwszy rzut oka może dziwić, że włączenie kompresji backup-u spowodo-wało skrócenie czasu jego wykonania (dru-ga pozycja w tabeli). Można to jednak bar-dzo prosto wytłumaczyć. Podczas tworze-nia archiwum, DB2 musi odczytać całą ba-zę danych, a następnie zapisać jej zawartość do pliku, będącego obrazem archiwum. W moim systemie baza była utworzona na dys-ku C, natomiast obraz archiwum tworzony był na dysku D. Wąskim gardłem był zapis na dysk, który zawierał docelowy obraz ar-chiwum. Odczyt stron z dysku i ich kom-presja w locie była szybsza niż sam zapis na dysk D. Najlepszy rezultat został osiągnię-ty dla kompresowanego backup-u i skom-presowanej już bazy, ponieważ w tym przy-padku ilość stron do skopiowania oraz za-pisania była najmniejsza. W ogólnym przy-padku czasy mogą się różnić. Jeśli dysponu-jemy dostatecznie dużą liczbą dysków, wte-dy kompresja backup-u najprawdopodob-niej spowolni proces tworzenia archiwum.

Natomiast niezależnie od liczby dysków, skompresowana wcześniej baza danych za-wsze będzie się backup-ować szybciej, po-nieważ będzie mniej stron do odczytania. Regułę tę potwierdza praktyka. Jeden z klientów DB2 i SAP ERP w Polsce przy wy-korzystaniu kompresji DB2 zmniejszył pra-wie o połowę rozmiar bazy danych (z 3 TB) i dzięki temu nie musiał zmieniać polityki backup-owej, by wykonać backup w założo-nym oknie czasowym.

WydajnośćBez dwóch zdań – kompresja zużywa moc procesora! Jeśli ktoś w teście wydajnościo-wym chce wycisnąć maksimum, przy dodat-kowym założeniu, że dysponuje nieskończenie szybką macierzą dyskową, wtedy kompresji włączać nie powinien. Oczywiście określenie nieskończenie szybką wydaje się być przesadą, choć nie do końca. W laboratoryjnych testach wydajnościowych (takich jak TPC-H) dokła-da się tyle dysków, aż uzyska się moment, w którym dyski odpowiadają na tyle szybko, że da się osiągnąć 100% utylizację procesorów. W rzeczywistych systemach, przede wszyst-kim ze względu na duże koszty, wymiaruje się systemy, zwracając w pierwszej kolejności uwagę na wymaganą przestrzeń dyskową. W rzeczywistych systemach także nie zakłada się 100% utylizacji procesorów – obciążenie jest niejednorodne i zawsze rezerwuje się ja-kiś zapas mocy na skoki przetwarzania. Dlate-go też w rzeczywistych systemach opartych o DB2 stosuje się kompresję – najczęściej w du-żych rozwiązaniach, np. takich jak SAP ERP czy SAP BI. Wnikliwszym czytelnikom po-lecam opracowanie Waldemara Gaidy DB2 9 Row Compression in a SAP, w którym opi-sał wpływ kompresji na wydajność przetwa-rzania na podstawie jednego z rzeczywistych projektów (Google: waldemar gaida sap com-pression). Ogólny wniosek z opracowania jest taki: dzięki kompresji procesy wsadowe dzia-łają szybciej, system charakteryzuje się lep-szymi czasami odpowiedzi, wzrasta śred-nia utylizacja procesorów. Tak jak już wcze-śniej wspomniałem, oszczędne formaty za-pisu danych na dyskach z jednej strony wpi-sują się w trendy rozwoju sprzętu, a z drugiej strony wpisują się w ideę Smarter Planet (Mą-drzejszej Planety), propagowaną przez IBM, zmierzającą do oszczędności oraz inteligent-niejszego i optymalniejszego działania.

Listing 6. Rozmiary tabel ładowanych instrukcją SQL INSERT z automatyczną kompresją

TABLE DATA SIZE [KB] INDEX SIZE [KB]

---------- -------------------- --------------------

LINEITEM 339072 112128

ORDERS 79744 40064

PARTSUPP 45568 41600

CUSTOMER 15616 3072

PART 12032 3200

SUPPLIER 1792 384

NATION 256 256

REGION 256 256

Listing 7. Załadowanie tabeli XML.PARTSUPP danymi XML

declare k1 cursor for select row_number() over(), xmlrow( ps_partkey, ps_suppkey,

ps_availqty, ps_supplycost, ps_comment) from tpch.partsupp;

load from k1 of cursor insert into xml.partsupp ;

ARTuR WROńsKiOd piętnastu lat specjalizuje się w rozwiązaniach przetwarzania danych. Aktualnie pracuje jako kierownik zespołu technicznego IBM Information Management Software.Kontakt z autorem: [email protected]

Page 83: Sdj Extra 35 Db2
Page 84: Sdj Extra 35 Db2