O efektywności aplikacji uwag kilka…

Zegary procesorów osiągają coraz bardziej zawrotne prędkości (czytaj: częstotliwości), ale pomimo tego projektanci i programiści pracujący z Developerem/2000 nie mogą sobie pozwolić na zaniedbanie problemów związanych z efektywnym działaniem wytwarzanych za jego pomocą aplikacji. Ponadto, trudno jest zakładać, że klient, dla którego robimy oprogramowanie dysponuje samymi maszynami Pentium II z pamięcią RAM leżącą odłogiem. Na efektywność oprogramowania postrzeganą przez użytkownika mają wpływ głównie dwa składniki: efektywność przetwarzania informacji w bazie danych, gdzie pole do działania mają administratorzy i specjaliści od strojenia bazy danych (polecam w tym miejscu artykuły Pana Mikołajczyka) oraz efektywność działania aplikacji, która w dużym stopniu zależy od programisty aplikacji. W dalszej części artykułu jest zamieszczonych kilka wskazówek i podpowiedzi jak poprawić efektywność aplikacji wykonywanych za pomocą Developera/2000, a szczególnie Oracle Forms.

Używaj pełnych odwołań do elementów bloku w kodzie PL/SQL

W wyzwalaczach, procedurach i funkcjach pisanych w PL/SQL odwołania do elementów w blokach są bardziej efektywne jeśli jawnie podajemy nazwę bloku. Należy zatem używać odwołań w stylu :PR.PR_NAZWISKO, a nie :PR_NAZWISKO, nawet jeśli nazwa elementu PR_NAZWISKO jest unikalna w obrębie całego modułu. Ma to szczególne znaczenie w złożonych aplikacjach gdzie liczba bloków i pól jest duża. Oracle Forms nie musi wówczas za każdym razem przeszukiwać wszystkich bloków po kolei aby znaleźć element do którego się odwołujemy.

Minimalizuj dostęp do zmiennych systemowych

Dostęp do zmiennych systemowych, takich jak SYSTEM.CURRENT_ITEM, SYSTEM.CURSOR_VALUE, itp., jest dość mało efektywne. Następuje wtedy chwilowe wstrzymanie wykonania programu PL/SQL w celu odczytania wartości danej zmiennej systemowej, po czym wykonanie programu jest wznawiane. Jeśli w danej jednostce programu ta sama zmienna jest odczytywana więcej niż raz to korzystniej jest przypisać jej wartość do zmiennej PL/SQL i dalej posługiwać tą zmienną.

Stosuj zapytania z jawnymi kursorami

W bardzo wielu jednostkach programowych PL/SQL w aplikacji znajdują się zapytania SQL-owe. Zapytania typu SELECT pr_nazwisko INTO :b1.pole FROM pracownicy; lub SELECT user INTO :b1.uzytkownik FROM dual; powodują otwieranie niejawnych kursorów. Nawet przypisanie typu :blok.pole := :sysdate; powoduje wykonanie SELECT sysdate INTO :blok.pole FROM DUAL; a co za tym idzie użycie niejawnego kursora. Użycie niejawnego kursora jest mniej efektywne od zastosowania jawnego kursora. Niejawne kursory według standardu ANSI są zamykane jeśli operacja FETCH nie zwróci kolejnego rekordu spełniającego zadane kryteria powodując wygenerowanie odpowiedniego wyjątku. W sytuacji kiedy odczytujemy tylko jedną krotkę, a takie zapytania są wykonywane najczęściej (wszystkie operacje „lookup” w wyzwalaczach POST-QUERY odczytujące krotki wg wartości klucza podstawowego), liczba operacji FETCH wynosi 2 (czyli dwa razy więcej niż potrzeba) plus obsługa wyjątku. Użycie jawnego kursora w postaci OPEN Kursor; FETCH Kursor INTO …; CLOSE Kursor; powoduje, że zostanie wykonana tylko jedna operacja FETCH. Zamiana użycia niejawnych kursorów na jawne może wyraźnie poprawić efektywność wypełnienia rekordami złożonego wielorekordowego formularza, w którym dla każdego rekordu na wyzwalaczu POST-QUERY wykonywane są zapytania typu „lookup” wypełniające pola niebazowe.

Rozważnie używaj zapytań z DUAL;

Jeśli w procedurze potrzebne są wartości dwóch lub więcej zmiennych typu SYSDATE lub USER, użyj łączonego zapytania SELECT sysdate, user INTO zmienna1, zmienna2 FROM DUAL; zamiast dwóch niezależnych zapytań.
Nie używaj zapytań z DUAL do wyliczania wartości wyrażeń w stylu SELECT substr(:PR.PR_IMIE,1,1) INTO zmienna FROM DUAL; – wystarczy zwykłe podstawienie zmienna:=substr(:PR.PR_IMIE,1,1);.
Nawet w przypadkach gdy funkcja języka SQL nie może być stosowana w instrukcji podstawienia, można często uniknąć stosowania zapytań z DUAL. Na przykład, bardzo użyteczna funkcja DECODE może pojawić się tylko w klauzuli SELECT zapytania SQL. Nie można zatem wpisać podstawienia zmienna := decode(x,wartosc,wynik1,wynik2); ale można zastosować SELECT decode(x,wartosc,wynik1,wynik2) INTO zmienna FROM DUAL;. Jeśli taka operacja jest wykonywana wielokrotnie korzystnie jest napisać własną funkcję DECODE składowaną w bazie danych, którą będzie można stosować w instrukcjach podstawienia.

create function OJ_DECODE(
x varchar2 default null,
wartosc varchar2 default null,
wynik1 varchar2 default null,
wynik2 varchar2 default null) return varchar2 is
begin
if nvl(x,”null”)=nvl(wartosc,”null”) then
return wynik1;
else
return wynik2;
end if;
end

Funkcję można oczywiście rozbudować, tak aby można było przekazywać więcej parametrów. Dodatkową korzyścią oprócz wzrostu efektywności jest uproszczenie kodu w tych miejscach gdzie funkcja MOJ_DECODE zostanie użyta, np. w sytuacjach kiedy wynik funkcji DECODE jest parametrem przekazywanym do innej funkcji możemy teraz zamiast kilku linii kodu ze zmiennymi pomocniczymi zapisać wszystko jako jedno wyrażenie.

Użyj indeksów dla list wartości

Nawet trzy sekundy czekania na pojawienie się okienka z listą wartości może być dla użytkownika dosyć denerwujące, zwłaszcza jeśli często z takiej listy korzysta. Standardowo w bazie danych są zakładane indeksy na kluczach podstawowych i kluczach obcych, ale listy wartości dokonują często selekcji rekordów na podstawie innych atrybutów opisowych z użyciem wartości wpisanych przez użytkownika (np. podczas walidacji pola za pomocą LW). Należy zatem wziąć pod lupę listy wartości oparte na dużych relacjach i dokonać analizy zapytań znajdujących się w definicjach grup rekordów. Na atrybutach, według których następuje selekcja rekordów w listach wartości, należy dodać odpowiednie indeksy.

Zmień liczbę buforowanych rekordów

Nawigację do kolejnych rekordów w bloku może znacznie poprawić zwiększenie liczby buforowanych rekordów we własnościach bloku. Operację tą trzeba jednak wykonywać ostrożnie gdyż zwiększenie liczby buforowanych rekordów powoduje zarezerwowanie określonego miejsca w pamięci operacyjnej. Ma to sens tylko wówczas gdy na stacji roboczej PC jest wystarczająco dużo pamięci operacyjnej. Jeśli pamięci jest mało (np. 16 MB) to duża liczba buforowanych rekordów może spowodować, że będą one buforowane nie w pamięci tylko w pliku wymiany Windows z widocznym dla nas „na czerwono” wysiłkiem twardego dysku, a takie rozwiązanie jest już mało sensowne.

Dokonaj modularyzacji menu

Jeśli menu systemu aplikacji jest bardzo rozbudowane i w różnych formularzach nawigujemy do różnych fragmentów tego samego menu, to czas uruchamiania formularza zwiększa się o czas nawigacji do odpowiedniego elementu menu. Korzystniej jest podzielić złożony moduł menu na kilka mniejszych.

Dokonaj modularyzacji formularzy

Zamiast dużych formularzy zawierających wiele bloków i wiele widoków korzystniej jest wykonać kilka mniejszych modułów i wołać je za pomocą CALL_FORM lub OPEN_FORM. Spowoduje to skrócenie czasu uruchamiania formularza.

Użyj D2KINIT w 16-bitowych MS Windows

Razem z Developer/2000 Release 1.3.2 dla Windows dostarczany jest program o nazwie
D2KINIT (w starszej wersji 1.3 nosił on nazwę CDEINIT), którego wywołanie można umieścić w grupie Autostart Windows. Powoduje to załadowanie z wyprzedzeniem do pamięci części kodu używanego przez programy runtime Developera/2000, a tym samym skrócenie czasu ładowania programu w chwili kiedy żąda tego użytkownik.

Stosuj biblioteki zapisane w formacie .PLX

Oracle Forms od wersji 4.5.6 pozwala na tworzenie bibliotek w formacie .PLX, które
zawierają wyłącznie kod skompilowany bez kodu źródłowego. Stosowanie plików .PLX zamiast .PLL zwiększy efektywność działania aplikacji. Jeśli mamy gotową bibliotekę PL/SQL zapisaną na przykład w pliku LIB.PLL to aby utworzyć plik LIB.PLX należy posłużyć się następującym poleceniem: f45gen.exe lib.pll username/password module_type=library W modułach formularzy należy dołączyć bibliotekę bez specyfikowania ścieżki i rozszerzenia pliku. Uwaga: w trakcie generowania formularza do formatu .FMX plik .PLL musi być nadal dostępny. Przerabianie plików .PLL na .PLX może powodować zostawianie na dysku plików typu TMP0.PLL, które można spokojnie usunąć.

Dokonaj materializacji agregatów

W aplikacjach wyświetlamy często wartości zagregowane wyliczone poprzez przeliczanie wielu rekordów odczytanych z bazy danych. Wyświetlany agregat może być liczbą interesujących nas rekordów (count), sumą wartości (sum), średnią (avg) lub być wynikiem dowolnej innej operacji grupowej. Rozważmy prosty przykład aplikacji, w której w bloku wielorekordowym wyświetlającym jednostki organizacyjne firmy jest wyświetlana liczba pracowników zatrudnionych w każdej jednostce. Istniejące rozwiązanie może polegać na tym, że w wyzwalaczu POST-QUERY, dla każdego rekordu po kolei, jest wykonywane zapytanie SELECT count(*)… liczące pracowników każdej jednostki organizacyjnej. Wypełnianie bloku rekordami może trwać bardzo długo. Rozwiązaniem problemu jest dodanie kolumny w tabeli z jednostkami organizacyjnymi i pamiętanie w bazie danych liczby pracowników bezpośrednio przy każdej jednostce organizacyjnej. Utrzymanie aktualnych liczb pracowników w poszczególnych jednostkach wymaga dodania odpowiedniego wyzwalacza na relacji z pracownikami. Wyzwalacz ten może wyglądać następująco:

create trigger liczba_pracownikow
after insert or delete or update of pr_jo_jednostka_id on pracownicy
for each row
begin
if inserting then
update jednostki_org
set jo_liczba_prac = nvl(jo_liczba_prac,0) + 1
where jo_jednostka_id = :new.pr_jo_jednostka_id;
elsif deleting then
update jednostki_org
set jo_liczba_prac = jo_liczba_prac – 1
where jo_jednostka_id = :old.pr_jo_jednostka_id;
else update jednostki_org
set jo_liczba_prac = jo_liczba_prac – 1
where jo_jednostka_id = :old.pr_jo_jednostka_id;
update jednostki_org
set jo_liczba_prac = nvl(jo_liczba_prac,0) + 1
where jo_jednostka_id = :new.pr_jo_jednostka_id;
end if;
end;

Jeśli tworzymy taki wyzwalacz na relacji, w której znajdują się już dane, należy pamiętać o wykonaniu operacji update, która zmaterializuje aktualne wartości agregatów w nowo dodanej kolumnie. W przypadku bardziej złożonych agregatów niż liczba rekordów, materializacja agregatów może okazać się jedynym akceptowalnym rozwiązaniem.

W przypadku trudnych aplikacji użyj PECS

Newralgiczne aplikacje systemu wymagające szczególnie efektywnego działania lub aplikacje, które z niewiadomych przyczyn działają bardzo wolno można poddać analizie za pomocą programu PECS (Performance Event Collection Services). Narzędzie to pozwala na uzyskanie odpowiedzi na szereg interesujących pytań, takich jak np.: Jak długo wykonuje się dany wyzwalacz? Jak wiele zamówień w ciągu godziny jest w stanie obsłużyć nasz formularz? Ile czasu zajmuje skorzystanie z listy wartości na danym polu? Ponadto, narzędzie może wspomóc sam proces testowania aplikacji. Możemy sprawdzić, które wyzwalacze nie zostały wywołane (czyli nie zostały również sprawdzone), które linie kodu nie zostały wykonane w trakcie testu, które widoki formularza nie zostały wyświetlone, itp.


Maciej Matysiak