background image

 

Rozdział 25 

Optymalizacja wydajności 
w aplikacjach typu klient/serwer 

W rozdziale tym omówiliśmy kilka metod optymalizowania wydajności aplikacji 
typu klient/serwer, tworzonych w środowisku Delphi. Trzy podrozdziały omawiają 
kolejno: optymalizację wydajności aplikacji w Delphi, optymalizację serwera 
i optymalizację wydajności w sieci. Zwróćmy uwagę na to, że podane tu 
wskazówki w żaden sposób nie wyczerpują zagadnień związanych z wydajnością. 
Autor opisuje w zwięzły sposób wiele sposobów optymalizacji. Głównym celem 
tego rozdziału jest wprowadzenie Czytelnika do wielu różnych technik, jakie stoją 
do jego dyspozycji. Nie zastąpi to jednak dokładnej i szczegółowej znajomości 
posiadanego systemu DBMS, sieci i platformy serwera. 

UWAGA: 

W rozdziale niniejszym poruszamy szereg zagadnień związanych z optymalizacją 
wydajności w 

kilku różnych platformach DBMS. Szczegółowe informacje 

o optymalizowaniu aplikacji klient/serwer w poszczególnych systemach DBMS 
można znaleźć w rozdziałach 15-18. 

Jaka szybkość działania jest wystarczająca? 

Przed przystąpieniem do optymalizacji musimy się zastanowić, co konkretnie jest 
naszym celem. Niestety optymalizacja wydajności rzadko może mieć charakter 
prewencyjny; dopóki system działa w miarę zadowalająco, poświęcamy jej mało 
uwagi. Tak więc trzeba zacząć od ustalenia, czego dokładnie oczekujemy. Jaka 
szybkość okaże się wystarczająca dla naszych celów? Po ustaleniu, co chcemy 
osiągnąć, możemy się zastanowić, jak to osiągnąć.  

Kryteria oceny wydajności 

Drugim etapem optymalizacji powinno być ustalenie, jakie cechy 
optymalizowanego systemu możemy modyfikować. Musimy poznać parametry 
wydajności, czyli takie wielkości, których zmiana może spowodować jej 
zwiększenie. Przykładami takich parametrów są zmienne konfiguracyjne serwera, 

background image

716 

Część IV 

wartości pól obiektów w Delphi, ustawienia sieciowe itd. Jednym z głównych 
celów niniejszego rozdziału jest zasugerowanie Czytelnikowi, jakie parametry 
może on poddać modyfikacji w celu zoptymalizowania wydajności aplikacji typu 
klient/serwer, napisanych i rozwijanych w Delphi. 

Środowisko testowe 

Środkiem stojącym do naszej dyspozycji jest serwer testowy. Na początku pracy 
nie zmieniajmy kodu użytkowego ani parametrów serwera. Jest przynajmniej kilka 
powodów, aby tego nie robić. Przede wszystkim nie chcemy przecież, żeby nasze 
testy spowodowały utratę lub przekłamania danych. Wszak nigdy nie 
uruchamiamy sprawdzanego kodu w systemach użytkowych (production systems
lub takich, z których właśnie korzystają inne osoby. Drugi powód, dla którego 
powinniśmy unikać testowania systemu podczas jego normalnej pracy wynika 
z ryzyka,  iż inni użytkownicy wywrą niepożądany i nieprzewidziany wpływ na 
wyniki testowania. W takich sytuacjach testy często generują przedziwne rezultaty, 
których na pozór nie sposób wyjaśnić. 

Sposoby określania wydajności 

Teraz, kiedy już poruszyliśmy podstawowe zagadnienia związane z optymalizacją, 
czas na wyjaśnienie, co dokładnie należy rozumieć pod pojęciem  wydajności 
(performance). Wydajność systemu można określać na wiele sposobów. 
Najczęściej korzystamy przy tym z następujących wskaźników: 

„Szybkość w transakcjach na sekundę, TPS (Transactions per second) - ogólna 

przepustowość systemu, często stosowana jako miara wydajności. W przypadku 
optymalizacji według tego wskaźnika uwagę skupiamy przede wszystkim na 
ułatwieniu aplikacji dostępu do serwera, optymalizacji samego serwera 
i umożliwieniu współbieżności.  

„Czas odpowiedzi na zapytanie (query response time) - wydajność systemu 

można też mierzyć według czasu, przez jaki konkretne zapytanie zostaje 
wykonanie. Przy stosowaniu tego wskaźnika skupiamy się głównie na środkach 
prowadzących do skrócenia tego czasu.  

„Czas wykonania zadań wsadowych (batch job execution time) - nasza aplikacja 

kliencka może wymagać,  żeby dane zadanie wsadowe wykonywało się 
w ustalonym przedziale czasu. Zadanie może obejmować wiele zapytań, 
współpracę z urządzeniami zewnętrznymi lub inne procesy. W takim przypadku 
naszym głównym celem staje się skrócenie całkowitego czasu wykonania 
zadania.  

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

717 

„Wydajność interfejsu aplikacji (application responsiveness) - wydajność 

aplikacji klienckiej można określić według szybkości wyświetlania ekranów 
i generacji raportów. Musimy wówczas skupić się na złożonych zagadnieniach 
dotyczących wzajemnej współpracy między klientem a serwerem. 

„Sposób realizacji współbieżności (concurrency) - tworzonego przez nas 

systemu mogą dotyczyć narzucone z góry wymagania, dotyczące możliwości 
jednoczesnego dostępu. Wymaganie takie może na przykład narzucać na naszą 
aplikację zdolność zapewnienia tysiącu użytkownikom wprowadzania wierszy 
do tej samej tablicy w tym samym czasie. W takim przypadku musimy 
przeprowadzić optymalizację systemu pod kątem maksymalnej redukcji liczby 
procesów blokowania i konfliktów dostępu do zasobów (resource contention).  

Wydajność najczęściej określamy w sposób złożony, za pomocą kombinacji 
powyższych miar. Rzadko zdarza się, żeby wyłącznie jedna z nich mogła w sposób 
wyczerpujący określić wydajność systemu. Dzieje się tak między innymi z powodu 
wzajemnych powiązań między tymi miarami. Np. wskaźnik TPS możemy uznać za 
najważniejszą miarę wydajności w konkretnym zadaniu do wykonania z użyciem 
klienta, ale na ogólną przepustowość systemu z 

wieloma użytkownikami 

bezpośredni wpływ może mieć także sposób realizacji współbieżności. Jeżeli 
wzajemne blokady procesów będą często występowały, to spadek średniej 
wartości wskaźnika TPS jest bardzo prawdopodobny. Tak więc - nawet jeżeli 
jedną z miar wydajności uznamy za ważniejszą od pozostałych, to i tak zapewne 
okaże się,  że optymalizacja wymaga doboru wielu powiązanych ze sobą 
parametrów wydajności.  

Optymalizacja wydajności aplikacji 

Poniższe wskazówki dotyczą aplikacji w 

układzie klient/serwer. Ponieważ 

aplikacja pełni rolę bramy udostępniającej informacje zawarte w serwerze, to 
przyjęte w niej podejście do nabywania i prezentacji danych może mieć wielki 
wpływ na całkowitą wydajność systemu.  

Minimalizowanie liczby połączeń z serwerem 

Przede wszystkim należy unikać otwierania zbędnych połączeń z serwerem, 
którego zasoby są przecież ograniczone. Powoduje to niepotrzebne obciążenie jego 
zasobów, spowolnienie działania aplikacji klienckiej i może przyczynić się do 
przepełnienia sieci. Poniżej podajemy niektóre z technik limitowania liczby 
połączeń z serwerem. 

background image

718 

Część IV 

Rola komponentu TDatabase

 

Jeden ze sposobów ograniczania koniecznych połączeń z serwerem polega na 
korzystaniu tylko z jednego komponentu typu 

TDatabase

 w całej aplikacji. Oto 

wskazówki: 

1. Umieścić komponent 

TDatabase

 w głównym formularzu aplikacji. 

2. Nadać własności 

AliasName

 komponentu 

TDatabase

 wartość wskazującą 

na ten alias BDE, którego ma używać aplikacja. 

3. Własności 

DatabaseName

 nadać wartość  będącą nazwą, która ma zostać 

udostępniona w aplikacji jako lokalny alias.  

4. Używając komponentu 

DataSet

 (np. 

TTable

TQuery

 lub 

TStoredProc

), jego własności 

DatabaseName

 należy nadawać wartość 

taką, jak dla 

TDatabase (

zamiast aliasu BDE). 

Po otwarciu tak zainicjowanego komponentu 

TDataSet

, nie utworzy on swojego 

własnego połączenia, ale skorzysta z już istniejącego - udostępnionego przez 
komponent 

TDatabase

.  

Istnieje jednak jedno ograniczenie, o którym musimy pamiętać, gdy serwerowi 
udostępniamy połączenie z użyciem komponentu 

TDatabase

, a nie oddzielnego 

aliasu BDE. Podczas pracy z kreatorem formularzy (form designer), otwierając 
komponent typu 

TDataSet

, dotyczący aliasu dla aplikacji, powinniśmy 

przestrzegać następujących wskazówek: 

„formularz z komponentem 

DTatabase

 trzeba otwierać również w kreatorze 

formularzy 

„Otwarcie komponentu 

TDataSet

 (przez nadanie wartości 

True

  własności 

Active

 w oknie Inspektora obiektów - Object Inspector), który dotyczy 

komponentu 

TDatabase,

 automatycznie powoduje nawiązanie połączenia 

z serwerem.  Ponieważ 

True

  (prawda) jest domyślną wartością  własności 

KeepConnection

  (utrzymaj połączenie)  tego komponentu, zamknięcie 

TDataSet

  nie  spowoduje zamknięcia sesji z serwerem. Zamiast tego, gdy 

zachowamy projekt i zakończymy pracę z Delphi, status dla 

TDatabase

 

zostanie zachowany wraz z nim. Powtórnie wczytując projekt, będziemy 
musieli wpisać hasło, ponieważ komponent 

TDatabase

 spróbuje ponownie 

nawiązać połączenie z serwerem. Zmienić to możemy nadając własności 

KeepConnection

 wartość 

False

  (falsz); wówczas jednak w sytuacji, gdy 

nie ma aktywnych komponentów 

DataSet

 (bo wszystkie już zamknięto) 

będziemy musieli ponownie logować się w serwerze przed każdą próbą 
otwarcia nowego komponentu 

DataSet

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

719 

SQL PASSTHRU MODE 

Wyrażenie 

SQL PASSTHRU MODE

, służące do określania aliasu bazy danych, 

daje nam następną możliwość redukcji liczby połączeń z serwerem. Parametr 

SQL 

PASSTHRU 

może być ustawiony dla rodziny programów obsługi (driver) albo dla 

wybranych aliasów. Jeżeli ustawiliśmy go dla rodziny programów obsługi, to 
będzie on dotyczyć tylko nowo definiowanych aliasów, a nie już istniejących. 
Parametr ten może przyjmować jedną z trzech wartości: 

NOT SHARED

SHARED 

AUTOCOMMIT i SHARED NOAUTOCOMMIT

. Dwie ostatnie wartości pomagają 

w utrzymaniu małej liczby połączeń z serwerem, ponieważ zezwalają środowisku 
BDE na współdzielenie (share) połączeń, nawiązanych z serwerem przez naszą 
aplikację. 

Formularze dynamiczne 

Jeszcze jeden sposób ograniczania całkowitej liczby połączeń z serwerem to 
tworzenie formularzy dopiero wówczas, gdy są rzeczywiście potrzebne. Domyślnie 
wszystkie formularze w aplikacji tworzy się zaraz po jej uruchomieniu, nawet 
jeżeli tylko część z nich jest kiedykolwiek jednocześnie używana. O wiele lepiej 
jest samodzielnie tworzyć i usuwać formularze o drugorzędnym znaczeniu, niż 
pozwolić im na niepotrzebne zajmowanie połączeń bazy danych z serwerem 
i innych zasobów.  

Zaprogramowanie jawnego tworzenia i usuwania formularzy, które nie mają być 
automatycznie tworzone, nie jest trudne. Oto metoda dynamicznego tworzenia 
i usuwania formularzy: 

1. Otworzyć okno dialogowe 

Project Options

 w Delphi 

2. Przesunąć wszystkie formularze o drugorzędnym znaczeniu z listy 

Auto-create

 

forms

 do listy 

Available forms

 

3. Dla każdego dynamicznie tworzonego formularza w kodzie obsługi zdarzenia 

OnClose

 umieścić przypisanie 

Action:=caFree

 

4.  Do utworzenia formularza (w celu jego wyświetlenia) należy użyć konstrukcji 

Application.CreateForm

  (

TForm1

Form1

), wpisując - zamiast 

TForm1

 - typ klasy dla formularza, a zamiast 

Form1

 - konkretną zmienną 

z nim związaną.  

5. Wyświetlić formularz z użyciem metody 

ShowModal

6. Usunąć formularz przez jego zamknięcie. 

background image

720 

Część IV 

Język SQL a wydajność 

Kilka następnych wskazówek dotyczy optymalizowania komunikacji pomiędzy 
aplikacją a serwerami, realizowanej z użyciem języka SQL. Ponieważ SQL jest 
uniwersalnym językiem systemów DBMS typu klient/serwer, jego dobra 
znajomość jest bardzo pożądana.  

Stosowanie procedur pamiętanych 

Kod skompilowany wykonuje się szybciej niż interpretowany, niezależnie od 
języka programowania. Zasada ta jest ogólnie prawdziwa także w przypadku SQL. 
Wszędzie, gdzie to możliwe, stosujmy procedury pamiętane (stored procedures
z parametrami zamiast komponentów 

TQuery

 i dynamicznego języka SQL 

środowiska Delphi. Zapewni to większą szybkość działania, niż w przypadku 
wysyłania dynamicznych wyrażeń SQL, ponieważ serwer kompiluje i z góry 
optymalizuje procedury pamiętane. Program interpretowany musi przechodzić 
przez proces tłumaczenia i optymalizacji za każdym razem, gdy chcemy go 
wykonać. Tak więc - im więcej możemy zaoszczędzić na etapie kompilacji, tym 
lepiej.  

Autor sądzi jednak, że jest jeden obszar zastosowań, w którym częste używanie 
procedur pamiętanych jest niewłaściwe. Dotyczy on zwykłych modyfikacji danych 
- w rodzaju tych, jakie zwykle przeprowadzamy przy pomocy komend 

INSERT

UPDATE

 lub 

DELETE

. W ciągu ostatnich kilku lat pojawiła się tendencja do 

modyfikowania danych z wykorzystaniem procedur pamiętanych, a nie kontrolek 
obsługi danych (data-aware controls). Powody przytaczane ku temu są różne, od 
lepszej ochrony i wydajności do większego zakresu kontroli nad sposobami 
realizacji wyrażeń języka DML. Są to wszystko argumenty istotne, ale pomija się 
w nich  fakt,  że podejście takie osłabia celowość stosowania narzędzi w rodzaju 
Delphi, które służą do tworzenia i rozwijania aplikacji klient/serwer. W przypadku 
aplikacji tworzonych w 

Delphi jedynym sposobem automatycznego użycia 

procedur pamiętanych do normalnych modyfikacji danych jest wykorzystanie 
komponentu typu 

TUpdateSQL

. Chociaż komponent taki radzi sobie zupełnie 

dobrze, i tak wymaga od nas zaprogramowania w języku SQL konkretnego 
sposobu aktualizacji tabel. Nasze procedury pamiętane będą niewątpliwie 
wymagać listy wartości pól przy wstawianiu, usuwaniu lub modyfikacji wierszy 
tabeli. Napisany przez nas kod wymaga więc aktualizacji przy każdej zmianie 
struktury tabeli i dlatego podejścia takiego należy unikać.  

Nie oznacza to braku przydatności komponentu 

TUpdateSQL

. Zawsze jednak 

powinniśmy najpierw próbować wykorzystać wbudowany w Delphi mechanizm 
aktualizacji tabel. Modyfikacje z użyciem komponentu 

TUpdateSQL

 należy 

przeprowadzać tylko w razie absolutnej konieczności.  

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

721 

Wykonując aktualizacje za pośrednictwem procedur pamiętanych bez komponentu 

TUpdateSQL

, całkowicie tracimy wspomaganie ze strony udostępnianych przez 

Delphi kontrolek obsługi danych. Musimy wtedy ręcznie konfigurować zwykłe 
kontrolki tak, aby symulowały kontrolki obsługi danych na drodze ograniczenia 
akceptowanych typów danych, ściągania dla nich wartości po pierwszym 
wyświetleniu formularza i wreszcie wysyłania zmodyfikowanych wartości do 
serwera poprzez procedury pamiętane po zamknięciu formularza. Opisana strategia 
jest  żmudna i prowadzi do błędów. Ponadto stawia pod znakiem zapytania sens 
korzystania z narzędzi w rodzaju Delphi, a dokładniej z ich zdolności do działania 
na danych z serwera. W takim przypadku moglibyśmy po prostu użyć narzędzia 
bez wbudowanych udogodnień komunikacji z serwerem, bezpośrednio wywołując 
funkcje API do obsługi baz danych.  

Inną wadą takiej koncepcji jest fakt, że prowadzi ono do omijania mechanizmów 
ochrony zapewnianych przez serwer. Większość narzędzi do administrowania 
systemem nie pozwala nam zorientować się, czy np. procedura pamiętana XYZ ma 
przywilej 

DELETE

 w 

odniesieniu do tablicy XYZ. Tracimy możliwość 

przeglądania z jednego punktu obserwacyjnego konfiguracji ochrony w systemie, 
czyli praw nadanych (lub anulowanych) użytkownikom lub grupom do 
indywidualnych obiektów bazy danych. Nie wystarczą wówczas informacje 
o prawach, nadanych lub anulowanych przez nas za pomocą komend 

GRANT

 lub 

REVOKE

 - dodatkowo musimy jeszcze zapoznawać się z zawartością procedur 

pamiętanych naszej bazy danych. 

UWAGA: 

Inaczej niż w przypadku systemów Sybase, Oracle i Microsoft SQL Server, 
platforma InterBase dopuszcza, żeby procedurom zdarzeń nadawane były 
(i anulowane) prawa do obiektów bazy danych, tak jakby procedury te były 
użytkownikami. Związane z tym informacje dotyczące ochrony przechowuje się 
jako część danych serwera. Informacji tych mogą dotyczyć zapytania ze strony 
narzędzi do administrowania bazami danych - tak więc możliwe jest uzyskanie 
obszernego przeglądu stanu ochrony w serwerze. Autor jednak w dalszym ciągu 
uważa taki sposób realizacji ochrony baz danych za niewłaściwy.  

Dawniej narzędzia do tworzenia aplikacji klient/serwer były na tyle prymitywne, 
że przeprowadzanie prostych modyfikacji bazy danych poprzez procedury 
pamiętane było złem koniecznym. Czas ten jednak powoli przechodzi do 
przeszłości, w związku czym tylko na korzyść wyjdzie nam, jeśli postaramy się 
w pełni wykorzystać wbudowane w Delphi mechanizmy służące modyfikowaniu 
danych. Zapamiętajmy następującą regułę: procedur pamiętanych używamy do 
realizacji złożonych zapytań oraz do zadań innych, niż proste manipulacje na 
danych. 

background image

722 

Część IV 

Korzystanie z metody Prepare

 

Metodę 

Prepare

  (przygotuj) komponentów 

TQuery

 powinniśmy wywoływać, 

zanim je otworzymy. 

Prepare

 wysyła zapytanie SQL do programu obsługi baz 

danych (database engine), aby zostało ono poddane analizie syntaktycznej 
(parsing) oraz optymalizacji. Jeżeli metodę  tę wywołuje się jawnie dla 
dynamicznego zapytania SQL, które ma być wykonywane wiele razy (a nie jeden 
raz), Delphi wysyła tylko parametry takiego zapytania - a nie cały jego kod - za 
każdą jego realizacją. Jeżeli metody 

Prepare

 nie wywoła się jawnie z góry, to 

zapytanie będzie automatycznie przygotowywane przy każdym otwarciu. Dzięki 
przygotowaniu zapytania z góry, zdejmujemy ten obowiązek z programu obsługi 
baz danych - w efekcie będziemy mogli je otwierać i zamykać wiele razy pod rząd, 
bez konieczności ponownego przygotowywania. Z całą pewnością przyczyni się do 
przyspieszenia operacji często wykorzystujących zapytania.  

Własność UpdateMode 

Zarówno typ 

TTable

, jak i 

TQuery

 propagują własność 

UpdateMode

. Określa 

ona typ klauzuli języka SQL 

WHERE

, której używamy do modyfikowania danych 

za pomocą kontrolek obsługi danych (data-aware controls). Wartością domyślną 
jest 

UpWhereAll -

 co oznacza, że BDE generuje klauzulę 

WHERE

, która 

wyświetla każdą kolumnę tabeli. Takie działanie może okazać się bardzo mało 
wydajne, zwłaszcza dla dużych tabel. Alternatywną i szybszą koncepcją jest 
skorzystanie z ustawienia 

UpWhereChanged

. Powoduje ono wygenerowanie 

klauzuli 

WHERE

, w której występują tylko kluczowe pola tabeli - wraz z polami, 

które uległy zmianie. Ilustruje to poniższy przykład.  

Załóżmy, że nasza aplikacja w Delphi zmieniła właśnie pole LastName (nazwisko) 
tabeli CUSTOMER. Oto kod SQL, wygenerowany przy ustawieniu 

UpWhereAll

 

- zwróćmy uwagę na długą klauzulę 

WHERE

UPDATE CUSTOMER 
SET LastName=’newlastname’ 
WHERE CustomerNumber=1 
AND LastName=’Doe’ 
AND FirstName=’John’ 
AND StreetAddress=’123 SunnyLane’ 
AND City=’Anywhere’ 
AND State=’OK’ 
AND Zip=’73115’ 

A oto wyrażenie wygenerowane przy ustawieniu 

UpWhereChanged

UPDATE CUSTOMER 
SET LastName=’newlastname’ 
WHERE CustomerNumber=1 
AND LastName=’Doe’ 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

723 

Zauważmy, o ile krótszy jest drugi program. Poza tym, dzięki umieszczeniu w nim 
starej wartości pola 

LastName

 w klauzuli 

WHERE

, uniknięto niebezpieczeństwa 

nadpisania modyfikacji tego pola, wprowadzonych przez innego użytkownika. 
Jeżeli inny użytkownik zmieniłby pole 

LastName

 w czasie pomiędzy odczytem 

wiersza a jego uaktualnieniem przez użytkownika bieżącego, wyrażenie 

UPDATE

 

wygenerowane przy ustawieniu 

UpWhereChanged

 zakończy się niepomyślnie 

i tego  właśnie sobie życzymy. Ta metoda zapewnia nieco mniejszą odporność na 
błędy niż 

UpWhereAll

. Inny użytkownik mógłby usunąć wiersz po jego odczycie 

przez naszą aplikację, a potem dodać nowy rekord do tabeli, który przypadkiem 
będzie miał tę samą wartość klucza i pola 

LastName

, co stary rekord. Jeżeli dla 

naszego rekordu użyto by jego wyrażenia 

UPDATE

, uaktualniony zostałby 

niewłaściwy rekord. Taki scenariusz jest jednak bardzo mało prawdopodobny.  

Ustawienie własności

 UpdateMode

 na wartość 

UpWhereKeyOnly

 zmniejsza 

jeszcze odporność na błędy, ale też ma swoje zalety. Powoduje ono sprawdzanie 
tylko kluczowych wartości dla wiersza, który modyfikujemy - innymi słowy 
zakłada się w niej, że niemożliwa jest modyfikacja uaktualnianego przez nas pola 
w czasie, jaki upłynął od chwili pierwszego odczytania rekordu. Założenie takie 
może być bezpieczne, ale nie musi.  

OSTRZEŻENIE 

W większości aplikacji wielodostępnych (multi-user applications) założenie,  że 
rekordu nie zmodyfikowano od chwili jego pierwszego odczytu przez naszą 
aplikację kliencką, nie jest bezpieczne. Dlatego też przy ustawieniu 

UpWhere-

KeyOnly

 powinniśmy zachować dużą ostrożność. Przed wykorzystaniem go 

w aplikacji dla wielu użytkowników, musimy dobrze poznać wszystkie związane 
z nim ograniczenia.  

Ustawienie 

UpWhereKeyOnly 

zapewnia taki rodzaj optymalizacji, który należy 

stosować w 

rzadkich przypadkach i 

tylko w 

razie konieczności. Ponieważ 

generowana przy tym ustawieniu klauzula 

WHERE

 jest krótsza, w naturalny sposób 

zapewnia szybsze działanie od klauzul uwzględniających więcej kolumn. Przed 
użyciem tej opcji należy skonsultować się z administratorem bazy danych, gdyż 
nieumiejętne jej wykorzystanie może prowadzić do katastrofalnych skutków. 

„Aktualizowalne” zapytania typu TQuery 

Z reguły powinniśmy unikać aktualizowalnych zapytań 

TQuery

. Zamiast nich 

należy używać perspektyw serwera. Jest kilka powodów dla tego zalecenia. Po 
pierwsze, uaktualnianie zapytania typu 

TQuery

 zrzuca cały ciężar analizy 

zapytania SQL i aktualizacji związanych z nim tabel na aplikację-klienta, a nie na 
serwer (zwłaszcza w przypadku BDE), chociaż jest to zadanie dla serwera. 

background image

724 

Część IV 

To serwer ma odpowiednie środki i zasoby do przeprowadzania skomplikowanych 
operacji związanych z obsługą baz danych. Serwer ponadto lepiej zrealizuje swój 
własny dialekt SQL niż aplikacja kliencka. Zapytania „aktualizowalne” są 
wówczas elastyczniejsze, a uaktualnienia - szybsze.  

Uaktualnienia buforowane 

Z udostępnianego przez Delphi mechanizmu uaktualnień buforowanych (cached 
updates
) powinniśmy korzystać przy minimalizowaniu kodu SQL, który jest 
wysyłany do serwera i do zmniejszenia liczby blokad (locks) bazy danych, które 
powoduje nasza aplikacja. Uaktualnienia buforowane przechowuje się lokalnie - aż 
do chwili ich przeprowadzenia w bazie danych. Wtedy dopiero wysyła się je do 
serwera. Redukuje to liczbę blokad w serwerze i całkowity czas ich trwania, może 
więc znacznie przyspieszyć działanie aplikacji. Oto sposób wykorzystania 
uaktualnień buforowanych: 

1.  W oknie Inspektor Obiektów (Object Inspector) nadać wartość 

True

 własności 

CachedUpdates

 tego komponentu 

DataSet

, którego uaktualnienia chcemy 

buforować 

2. Nadać odpowiednią wartość  własności 

UpdateRecordTypes

 komponentu 

DataSet

, w celu ustalenia typu kontroli widocznych wierszy w buforowanym 

zbiorze. Własność ta może przyjmować następujące wartości: 

rtModified

rtInserted

rtDeleted

 i 

rtUnmodified

 

3. Utworzyć procedurę obsługi zdarzenia 

OnUpdateError -

 tak, żeby 

obsługiwała wszystkie błędy, jakie wystąpią podczas wywołania 

ApplyUpdates

 

4. Wprowadzić modyfikacje danych komponentu 

DataSet

 podczas działania 

aplikacji 

5. Zachować modyfikacje za pomocą 

ApplyUpdates

 lub unieważnić je poprzez 

CancelUpdates

 

Monitor SQL 

Monitor SQL (SQL Monitor) to narzędzie użyteczne przy przeglądaniu kodu SQL, 
który generuje nasza aplikacja. Możemy go np. wykorzystać w sytuacji, gdy 
chcemy sprawdzić kod SQL, wygenerowany dla operacji, które wydają się działać 
stanowczo zbyt wolno. Konieczne może okazać się zamiana kodu na perspektywę 
lub procedurę pamiętaną w serwerze. Możemy też odkryć,  że nasz sposób 
przeszukiwania lub aktualizowania danych jest mało wydajny i aplikację trzeba 
zoptymalizować. Niezależnie od konkretnej sytuacji, powinniśmy skorzystać 
z dodatkowych informacji, które udostępnia Monitor SQL. Znajduje się on 
w menu Database środowiska Delphi.  

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

725 

Buforowanie schematów 

System BDE udostępnia obecnie tzw. buforowanie schematów (schema caching), 
czyli lokalne zapamiętywanie informacji o strukturze obiektów bazy danych. 
Włączenie tej opcji może przyczynić się do zredukowania liczby zapytań 
wysyłanych przez aplikację do serwera w celu uzyskania danych katalogowych 
o bazie danych. Efektem może być znaczne przyspieszenie aplikacji, ponieważ 
w takim przypadku BDE nie musi ciągle otrzymywać tych danych z serwera.  

Buforowanie schematów włączamy za pomocą narzędzia  BDE Administration. 
buforowaniem schematów wiążą się cztery parametry (tabela 25.1). 

Tabela 25.1. Parametry konfiguracji BDE, które wpływają na buforowanie 

schematów 

Parametr Akcja 

ENABLE SCHEMA CACHE

 

Włączenie/wyłączenie buforowania schematów 
(parametr na poziomie sterownika) 

SCHEMA CACHE SIZE 

Liczba tabel, dla których należy buforować 
dane schematów  

SCHEMA CACHE TIME 

Czas (w sekundach) buforowania 

SCHEMA CACHE DIR  

Katalog, w 

którym schematy mają zostać 

zapamiętane (parametr na poziomie sterownika) 

Domyślną wartością parametru 

SCHEMA CACHE TIME

 jest 

-1

, co oznacza, że 

schematy pozostaną w buforze aż do zamknięcia bazy danych. Dopuszczalne 
wartości parametrów 

SCHEMA CACHE TIME

 należą do przedziału od 1 do 

2 147 483 647 sekund.  

Włączenie buforowania schematów może zauważalnie wpłynąć na wydajność 
aplikacji, zwłaszcza w przypadku połączeń w sieciach rozległych (WAN). Przy 
buforowaniu program BDE zakłada, że schemat bazy danych pozostaje statyczny (i 
jest to założenie naturalne). Wynika stąd jednak, że buforowanie schematów nie 
nadaje się do każdej bazy danych. W szczególności nie powinniśmy korzystać 
z buforowania w przypadku baz danych, w których: 

„często dodajemy lub usuwamy kolumny 

„często dodajemy lub usuwamy indeksy tabel 

„często zmieniamy atrybuty 

NULL

/

NOT NULL

 dla kolumn. 

Jeżeli jednak użyjemy buforowania w przypadku baz danych, które się do tego nie 
nadają, możemy spodziewać się następujących błędów SQL: 

background image

726 

Część IV 

„Unknown Column (nieznana kolumna) 

„Invalid Bind Type (nieprawidłowy typ wiązania) 

„Invalid Type (nieprawidłowy typ) 

„Invalid Type Conversion (nieprawidłowa konwersja typu) 

„Column Not a Blob (kolumna, nie blob

Filtry 

Filtry w Delphi służą do kwalifikowania zbiorów wynikowych po stronie klienta 
naszej aplikacji typu klient/serwer. Dla małych komponentów 

DataSet

  użycie 

procedury obsługi zdarzenia 

OnFilterRecord

 może okazać się wydajniejsze, 

niż ponawianie zapytań, ponieważ powoduje ograniczenie liczby interakcji 
z serwerem bazy danych i siecią. Małe zbiory wynikowe będą i tak często 
buforowane w całości po stronie komputera klienckiego, tak więc ich lokalne 
filtrowanie ma sens, jeśli tylko jest możliwe. Aby uaktywnić lokalne filtrowanie 
rekordów w aplikacji Delphi wykonujemy następujące czynności: 

1. Otwieramy program obsługi zdarzenia 

OnFilterRecord 

komponentu 

DataSet (

tak żeby uwzględniał/nie uwzględniał wierszy) z wykorzystaniem 

jego parametru 

Accept

 

2. Nadać wartość 

True

 własności 

Filtered

 komponentu 

DataSet

  

3. Przy wykorzystaniu komponentu 

DataSet

 przez naszą aplikację, będzie on 

widziany tak, jakby zawierał tylko wiersze spełniające kryteria filtrowania 

UWAGA  

Powyższe czynności dotyczą raczej zdarzenia 

OnFilterRecord

 niż własności 

Filter

 - przy ograniczaniu liczby wierszy wysyłanych przez aplikację. Wynika 

to z faktu, iż ustawienie 

Filter

 nie tworzy automatycznie filtrów lokalnych

Chociaż wyrażenie opisujące filtr na pewno ograniczy liczbę zwróconych wierszy, 
to ustawienie filtra z komponentem 

Table

, połączonym z serwerem bazy danych, 

spowoduje tylko modyfikację tworzonej klauzuli 

WHERE

. Tak więc mamy tutaj do 

czynienia z filtrem odległym, a nie lokalnym. 

Komponenty TField

 

Powinniśmy je stosować (wszędzie, gdzie tylko można) zamiast własności 

Fields

 typu 

TDataSet

 lub 

FieldByName

. Trwałe komponenty 

TField

 są 

wydajniejsze, gdyż przechowują podstawowe informacje o polu razem z aplikacją, 
dzięki czemu nie musi ona ich odtwarzać z BDE. Są one również bezpieczniejsze, 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

727 

ponieważ automatycznie zgłaszają wyjątek, jeśli zmienił się typ danych kolumny. 
Natomiast funkcja 

FieldByName

 oraz własność 

Fields

 muszą - by uzyskać 

podstawowe dane o kolumnie - przeszukiwać dane o schemacie tablicy. Są więc 
one wolniejsze i bardziej zawodne. Komponenty 

TField

 są łatwiejsze w użyciu, 

bezpieczniejsze i odporniejsze na modyfikacje obiektów bazy danych.  

Korzystanie ze słownika danych 

Autor uważa, że reguły logiki aplikacji należy w pierwszej kolejności umieszczać 
w serwerze,  jeżeli w ogóle jest to możliwe. W pewnych jednak sytuacjach okaże 
się,  że reguły logiki aplikacji musimy umieścić w naszej aplikacji klienckiej. 
Wbudowując reguły logiki aplikacji w aplikację, korzystajmy jak najczęściej 
z udostępnianego przez środowisko Delphi słownika danych (Data Dictionary
oraz jego zbiorów atrybutów (Attribute Sets). Po zdefiniowaniu w słowniku 
danych reguł logiki aplikacji dotyczących strony klienta, resztę reguł definiujemy 
z wykorzystaniem  komponentów 

DataSet

 i atrybutów 

TField

. Definiując 

reguły logiki aplikacji po stronie klienta z użyciem słownika danych, a nie 
w ramach konkretnej aplikacji, zapewniamy ich większą dostępność dla innych 
aplikacji.  

Pola tylko do odczytu a DBText

 

Do zapewnienia, że pole jest tylko do odczytu (read-only), używajmy komponentu 

DBText (

a nie 

DBEdit)

. Pola 

DBText

 zajmują mniej miejsca, a zapewniają tę 

samą funkcjonalność. Możemy też rozważyć celowość skorzystania ze statycznych 
komponentów 

Label

 dla kolumn danych, które nie mogą podlegać modyfikacjom 

od chwili, gdy formularz zostanie wyświetlony na ekranie. Jeżeli dane mogą zostać 
zmodyfikowane, musimy wykorzystać 

DBText. W 

przeciwnym razie możemy 

zaoszczędzić na zasobach, wymaganych nawet dla tak prostych kontrolek obsługi 
danych, jak 

DBText

, i w zdarzeniu 

OnShow

 formularza - skorzystać z własności 

Caption

 komponentu 

TLabel

. Używanie jak najprostszych kontrolek w naszej 

aplikacji przyczyni się nie tylko do mniejszego obciążenia zasobów, ale też - ze 
względu na mniejszą liczbę interakcji z bazą danych - do jej przyspieszenia.  

Wielowątkowe aplikacje bazy danych 

Jedną z najważniejszych zalet 32-bitowych systemów Windows jest umożliwienie 
tworzenia aplikacji wielowątkowych. Wątki tworzymy w Delphi - za pomocą 
obiektu 

TThread

 - w sposób łatwy i bezpieczny. Z wielowątkowości możemy też 

skorzystać w aplikacjach baz danych. Niezwykle pożyteczne jest wykonywanie 
zapytań w tle. Zapytanie o długim czasie realizacji można wykonać w jego 
własnym wątku tak, by aplikacja mogła nadal działać niezależnie. Wątki można też 

background image

728 

Część IV 

wykorzystać do przyspieszenia dostępu do bazy danych. Jeżeli np. odczytujemy 
rekordy ze zbioru plików systemowych i wstawiamy je do tabel w serwerze SQL, 
to dla każdego pliku można przydzielić oddzielny wątek - tak, żeby wstawienia 
z jednego pliku nie opóźniały wstawień z innego. Wielowątkowość można 
wykorzystać w aplikacjach baz danych na wiele sposobów. Listingi od 25.1 do 
25.4 przedstawiają prosty program bazy danych, korzystający z wielowątkowości 
przy jednoczesnym dostępie do trzech wierszy na raz. 

UWAGA  

W tej przykładowej aplikacji wykorzystano alias IBLOCAL, dostarczany wraz ze 
środowiskiem Delphi. Żeby go użyć musimy sprawdzić, czy uruchomiono już 
serwer systemu InterBase oraz czy baza danych IBLOCAL jest dla niego dostępna.  

Listing 25.1. Kod źródłowy projektu dla przykładowego 

programu wykorzystującego wielowątkowość o

 nazwie thrdex 

program thrdex; 
uses 
 Forms, 
 

thrdex00 in ‘thrdex00.pas’ {Form1}, 

 

thrdex01 in ‘thrdex01.pas’; 

{$R *.RES} 
begin 
 Application.Initialize; 
 Application.CreateForm(TForm1, 

Form1); 

 Application.Run; 
end. 

 

Listing 25.2. Kod źródłowy modułu thrdex00.pas 

- pierwszego 

dwóch modułów przykładowego programu thrdex, 

wykorzystującego wielowątkowość

 

unit thrdex00; 

interface 

uses 
 

Windows, Messages, SysUtils, Classes, Graphics,  

 

Controls, Forms, Dialogs,StdCtrls, DB, Grids, DBGrids,  

 

DBTables, Thrdex01, ExtCtrls; 

type 
 

TForm1 = class(TForm) 

 

 Query1: TQuery; 

 

 DataSource1: TDataSource; 

 

 Button1: TButton; 

 

 Query2: TQuery; 

 

 DataSource2: TDataSource; 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

729 

 

 Database1: TDatabase; 

 

 Session1: TSession; 

 

 Session2: TSession; 

 

 Database2: TDatabase; 

 

 Query3: TQuery; 

 

 DataSource3: TDataSource; 

 

 DBGrid3: TDBGrid;  

 

 Button2: TButton; 

 

 Database3: TDatabase; 

 

 procedure Button1Click(Sender: TObject); 

 

 procedure FormClose(Sender: TObject; var Action:  

 

 

 TCloseAction); 

 

 procedure Button2Click(Sender: TObject); 

 

 procedure Database1Login(Database: TDatabase;  

 

 

 LoginParams: TStrings); 

 private 
 

 { Private declarations } 

 public 
 

 { Public declarations } 

 end; 

var 
 Form1: 

TForm1; 

implementation 
var 
 

QueryThread1, QueryThread2 : TQueryThread; 

{$R *.DFM} 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
 Database1.Open; 
 Database2.Open; 
 QueryThread1:=TQueryThread.Create(Query1); 
 QueryThread2:=TQueryThread.Create(Query2); 
 QueryThread1.OnTerminate:=QueryThread1.OnTerm; 
 QueryThread2.OnTerminate:=QueryThread2.OnTerm; 
 Button1.Enabled:=False; 
end; 

procedure TForm1.FormClose(Sender: TObject; var Action: 
TCloseAction); 
begin 
 QueryThread1.Terminate; 
 QueryThread2.Terminate; 
end; 

procedure TForm1.Button2Click(Sender: TObject); 
begin 
 

with query3 do begin 

background image

730 

Część IV 

 

 

if active then close; 

  open; 
 end; 
end; 

procedure TForm1.Database1Login(Database: TDatabase; 
 LoginParams: 

TStrings); 

begin 
 

LoginParams.Values[‘USER NAME’] := ‘SYSDBA’; 

 

LoginParams.Values[‘PASSWORD’] := ‘masterkey’; 

end; 

end 

 

Listing 25.3 Plik .DFM formularza dla pliku thrdex00.pas 

modułem programu thrdex

 

object Form1: TForm1 
 

Left = 106 

 

Top = 67 

 

Width = 595 

 

Height = 434 

 

Caption = ‘Form1’ 

 

Font.Charset = DEFAULT_CHARSET 

 

Font.Color = clWindowText 

 

Font.Height = -11 

 

Font.Name = ‘MS Sans Serif’ 

 

Font.Style = [] 

 

OnClose = FormClose 

 

PixelsPerInch = 96 

 

TextHeight = 13 

 

object Button1: TButton 

  Left 

72 

  Top 

374 

  Width 

121 

  Height 

25 

 

 

Caption = ‘Start Query Threads’ 

  TabOrder 

  OnClick 

Button1Click 

 end 
 

object DBGrid3: TDBGrid 

  Left 

72 

  Top 

248 

  Width 

497 

  Height 

113 

  DataSource 

DataSource3 

  TabOrder 

  TitleFont.Charset 

DEFAULT_CHARSET 

  TitleFont.Color 

clWindowText 

  TitleFont.Height 

-11 

 

 

TitleFont.Name = ‘MS Sans Serif’ 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

731 

  TitleFont.Style 

[] 

 end 

 

 

object Button2: TButton 

  Left 

240 

  Top 

374 

  Width 

145 

  Height 

25 

 

 

Caption = ‘Open Query in Main Thread’ 

  TabOrder 

  OnClick 

Button2Click 

 end 
 

object Query1: TQuery 

  DatabaseName 

‘dbthread1’ 

  SessionName 

‘Ses1’ 

  SQL.Strings 

   ‘select 

from 

EMPLOYEE’) 

  Left 

16 

  Top 

 end 
 

object DataSource1: TDataSource 

  DataSet 

Query1 

  Left 

16 

  Top 

40 

 end 
 

object Query2: TQuery 

  DatabaseName 

‘dbthread2’ 

  SessionName 

‘Ses2’ 

  SQL.Strings 

   ‘select 

from 

SALES’) 

  Left 

16 

  Top 

80 

 end 
 

object DataSource2: TDataSource 

  DataSet 

Query2 

  Left 

16 

  Top 

120 

 end 
 

object Database1: TDatabase 

  AliasName 

‘IBLOCAL’ 

  DatabaseName 

‘dbthread1’ 

  LoginPrompt 

False 

  Params.Strings 

   ‘USER 

NAME=SYSDBA’ 

   ‘PASSWORD=masterkey’) 
  SessionName 

‘Ses1’ 

  OnLogin 

Database1Login 

  Left 

16 

  Top 

152 

 end 
 

object Session1: TSession 

  Active 

True 

  SessionName 

‘Ses1’ 

background image

732 

Część IV 

  Left 

16 

  Top 

224 

 end 
 

object Session2: TSession 

  Active 

True 

  SessionName 

‘Ses2’ 

  Left 

16 

  Top 

256 

  object 

Database2: 

TDatabase 

  AliasName 

‘IBLOCAL’ 

  DatabaseName 

‘dbthread2’ 

  LoginPrompt 

False 

  Params.Strings 

   ‘USER 

NAME=SYSDBA’ 

   ‘PASSWORD=masterkey’) 
  SessionName 

‘Ses2’ 

  OnLogin 

Database1Login 

  Left 

16 

  Top 

192 

 end 
 

object Query3: TQuery 

  DatabaseName 

‘dbdefaultthread’ 

  SQL.Strings 

   ‘SELECT 

FROM 

CUSTOMER’) 

 

 

Left = 16  

  Top 

288 

 end 
 

object DataSource3: TDataSource 

  DataSet 

Query3 

  Left 

16 

  Top 

320 

 end 
 

object Database3: TDatabase 

  AliasName 

‘IBLOCAL’ 

  DatabaseName 

‘dbdefaultthread’ 

  LoginPrompt 

False 

  Params.Strings 

   ‘USER 

NAME=SYSDBA’ 

   ‘PASSWORD=masterkey’) 
  SessionName 

‘Default’ 

  OnLogin 

Database1Login 

  Left 

16 

  Top 

352 

 end 
end 

 

Listing 25.4. K

od źródłowy modułu thrdex01.pas programu 

thrdex, wykorzystującego wielowątkowość

 

unit thrdex01; 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

733 

interface 

uses 
 

Classes, Forms, DBTables, Windows; 

type 
 

TQueryThread = class(TThread) 

 private 
 

 

{ Private declarations } 

  FQuery 

TQuery; 

 protected 
  procedure 

Execute; 

override; 

  procedure 

OpenQuery; 

 public 

 

  constructor 

Create(Query: 

TQuery); 

 

 

procedure OnTerm(Sender : TObject); 

 end; 

implementation 

{ TQueryThread } 

constructor TQueryThread.Create(Query: TQuery); 
begin 
 inherited 

Create(False); 

 

FQuery := Query; 

end; 

procedure TQueryThread.OpenQuery; 
begin  
 FQuery.Open; 
 

With FQuery do begin 

 

 

With Owner.Owner as TApplication do  

 

 

 ProcessMessages; 

 

 

While not EOF do Next; 

  Close; 
 

 

With Owner.Owner as TApplication do  

 

 

 ProcessMessages; 

 end; 
end; 

procedure TQueryThread.Execute; 
var 
 

Counter : Integer; 

begin 
 

{ Place thread code here } 

 

For Counter:=0 to 100 do begin 

  OpenQuery; 
 

 

If Terminated then exit; 

 end; 
end; 

background image

734 

Część IV 

procedure TQueryThread.OnTerm(Sender : TObject); 
begin 
 

Application.MessageBox(PChar(‘Thread running  

 

 ‘+FQuery.Name+’ Âfinished.’),PChar(FQuery.Name),  

 

 IDOK); 

end; 

end. 

W programie powyższym definiuje się dwa komponenty 

TSession

 i dwa wątki 

w tle.  Każdy z tych wątków kolejno otwiera i zamyka dane zapytanie 100 razy. 
Trzecie zapytanie można otworzyć na pierwszym planie. Jeżeli wpiszemy ten 
program (lub wczytamy z dołączonego dysku CD) i wykonamy go, to przekonamy 
się, że wątki w tle i wątki na pierwszym planie nie kolidują ze sobą. Każdy z nich 
wykonuje się niezależnie od pozostałych. Możemy - dla zapytania 
pierwszoplanowego - wiele razy klikać przycisk 

Open

, aby je ponownie wykonać, 

bez wywierania jakiegokolwiek wpływu na zapytania działające w tle. Delphi 
umożliwia wykorzystanie wielowątkowości w aplikacjach typu klient/serwer do 
przydzielania długo realizowanym zadaniom ich własnych wątków. Powyższy kod 
źródłowy zawiera wiele praktycznych szczegółów dotyczących wielowątkowości. 

Optymalizacja wydajności serwera 

Na optymalizację po stronie serwera składa się usprawnianie kodu SQL, ustawień 
sieciowych, konfiguracji bazy danych itd. Rzecz oczywista, optymalizacja serwera 
wpływa pozytywnie na wszystkie korzystające z niego aplikacje, nie tylko 
aplikacje Delphi.  

Optymalizowanie konfiguracji serwera 

Jest wiele metod optymalizacji serwerów baz danych w celu zwiększenia 
wydajności. Na początku mało który serwer jest optymalnie dopasowany do 
potrzeb choćby tylko nieco bardziej skomplikowanych aplikacji. Oto kilka 
potencjalnych przedmiotów optymalizacji.  

Pamięć 

Autor często obserwuje, że chociaż serwer ma mnóstwo zainstalowanej pamięci 
RAM, jego oprogramowanie obsługi baz danych nie zostało skonfigurowane 
w sposób,  umożliwiający jej wykorzystanie. Większość platform nie umie 
automatycznie dopasować systemu do sprzętu, i trzeba przeprowadzić ich „ręczną” 
konfigurację. Ogólnie rzecz ujmując, oprogramowaniu serwera musimy oddać do 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

735 

dyspozycji tyle pamięci RAM, ile tylko możemy - bez powodowania 
stronicowania (page swapping).  

Buforowanie  

Praktycznie rzecz ujmując, powinniśmy wyłączyć buforowanie na poziomie 
systemu operacyjnego i zostawić resztę serwerowi baz danych. Wie on więcej od 
nas o naszych danych i ich logicznej strukturze, i zwykle podejmuje lepsze decyzje 
co do przedmiotu buforowania.  

Innym ważnym zagadnieniem jest tzw. rozrost bufora procedur (procedure cache 
bloat). Zarówno Sybase, jak i Microsoft SQL Server dzielą pamięć buforową na 
dwie części:  bufor danych i bufor procedur  (procedure cache). Bufor procedur 
określa się w procentach całkowitej pamięci buforowej. Resztę pamięci zajmuje 
bufor danych. Oznacza to, że w systemach z dużą ilością pamięci RAM możliwy 
jest przerost (ponad potrzeby) bufora procedur. Jeżeli np. zwiększymy pamięć - ze 
128 do 256 megabajtów - to podwajamy też w efekcie rozmiar bufora procedur. 
Jeżeli dołożyliśmy tyle pamięci w celu zwiększenia bufora danych, a nie 
zmieniliśmy określenia wielkości bufora procedur, to część pamięci RAM 
pozostanie niewykorzystana. Należy wówczas rozważyć obniżenie współczynnika 
procentowego dla bufora procedur.  

Procesory 

Większość nowoczesnych systemów DBMS umożliwia rozproszenie obciążenia na 
wiele procesorów. Na przykład w systemie Microsoft SQL Server konfigurujemy 
bitową  maskę powinowactwa  (affinity mask), określającą, które procesory (w 
komputerze wieloprocesorowym) serwer może wykorzystać. Sybase ma z kolei 
parametr konfiguracyjny max online engines, określający, ile procesorów może 
użyć w komputerze wieloprocesorowym. Najelastyczniejszy jest system Oracle, 
w którym  obciążenie serwera można rozproszyć nawet na wiele komputerów, 
z wykorzystaniem  udostępnianej przez ten system techniki serwerów 
równoległych. Zauważmy jednak, że dodawania procesorów nie warto i nie można 
kontynuować w nieskończoność. Podwojenie liczby procesorów nie gwarantuje 
podwojenia wydajności. Dlaczego? Ponieważ  wąskie gardło dla operacji DBMS 
stanowią urządzenia wejścia/wyjścia, a nie procesory. Z drugiej jednak strony 
urządzeniami tymi również zarządza procesor - tak że przyspieszenie 
przetwarzania może również przyczynić się do pewnego zwiększenia ogólnej 
wydajności.  

Asynchroniczne wejście/wyjście 

Większość producentów platform DBMS udostępnia asynchroniczne operacje 
wejścia/wyjścia dla napędów dyskowych. Oczywiście asynchroniczny odczyt 
i zapis danych jest lepszy od synchronicznego. Jeżeli nasza platforma obsługuje 

background image

736 

Część IV 

asynchroniczną komunikację wejścia/wyjścia, sprawdźmy, czy wpływa ona 
dodatnio na wydajność. Zauważalna poprawa wydajności nastąpi najpewniej 
w przypadku korzystania z technologii smart drive lub RAID. Już chociażby ze 
względu na większą liczbę urządzeń, asynchroniczny odczyt/zapis zwiększa 
(fizycznie) przepustowość. 

Protokołowanie transakcji 

Protokoły transakcji/wycofań powinniśmy umieszczać na innych urządzeniach, niż 
dane. Nie tylko poprawia do wydajność, ale zwiększa niezawodność w zakresie 
odzyskiwania danych po awarii. Powinniśmy też rozważyć celowość 
zaoszczędzenia serwerowi pracy związanej z zarządzaniem protokołami, zwłaszcza 
w przypadku  właśnie tworzonych baz danych lub takich, których pełne kopie 
bezpieczeństwa tworzymy w trakcie rutynowego zachowywania danych. Np. - 
zarówno w systemie Microsoft SQL Server, jak i w Sybase SQL Server włączenie 
opcji 

trunc. log on chkpt

 powoduje usuwanie rekordów dotyczących zakończonych 

transakcji z protokołu - za każdym razem, gdy występuje kontrola systemowa 
(system checkpoint). Dzięki temu protokół jest względnie mały, operacje na nim 
wykonują się szybciej, a ogólna wydajność systemu poprawia się.  

OSTRZEŻENIE  

W systemach użytkowych należy ostrożnie korzystać z opcji usuwania rekordów 
z protokołu. Uaktywnienie usuwania rekordów oznacza brak możliwości 
wykonania kopii bezpieczeństwa protokołu. To zaś znaczy, że nie można 
wykonywać kopii przyrostowych (incremental backup) i że po ewentualnej awarii 
systemu nie będzie sposobu odtworzenia ostatnich modyfikacji. Musimy 
zdecydować, czy automatyczne uaktywnianie usuwania rekordów z protokołu 
transakcji odpowiada naszym celom. Czasem jest ono użyteczne, trzeba jednak 
dokładnie wiedzieć, co się robi. 

Optymalizowanie zapytań 

W całej książce pojawia się w różnych momentach termin optymalizator zapytań 
(query optimizer). Czym dokładnie on jest i co robi? Wszystkie lepsze platformy 
optymalizują zapytania SQL, kierowane do nich od klientów. Analizują przysłany 
kod SQL i ustalają (z różnym zresztą skutkiem) najbardziej wydajny sposób jego 
wykonania. Analizę  tę wykonuje właśnie optymalizator zapytań. W przypadku 
większości serwerów strategię opracowaną przez optymalizator określa się jako 
plan wykonania zapytania  (query execution plan). Podczas układania tego planu 
brane są pod uwagę takie czynniki, jak dostępność indeksów, obszar dysku do 
przeszukania, statystyka dotycząca dystrybucji kluczy w indeksach itd. Najczęściej 
plan taki jest optymalny, czasem jednak serwerowi trzeba pomóc w jego ułożeniu.  

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

737 

Pomagamy optymalizatorowi zapytań 

Jednym ze sposobów pomocy jest aktualizacja statystyk dotyczących indeksu, 
które przechowuje optymalizator. Zawierają one informacje o rozkładzie wartości 
kluczowych w indeksie. Zapewniając aktualność tych informacji, pomagamy 
optymalizatorowi zapytań w podejmowaniu właściwych decyzji.  

W InterBase statystykę indeksu uaktualniamy za pomocą następującej konstrukcji: 

SET STATISTICS INDEX INVOICES03 

gdzie 

INVOICES03

 jest nazwą indeksu do ponownego przetworzenia.  

W systemach Sybase i Microsoft kod jest następujący: 

UPDATE STATISTICS INVOICES.INVOICES03 

gdzie 

INVOICES

 jest nazwą tabeli, dla której zbudowano indeks, a 

INVOICES03

 

jest nazwą samego indeksu. Ponieważ indeksy w SQL Server są unikalne tylko 
w ramach swoich tabel, w komendzie musi wystąpić nazwa tabeli. Z drugiej strony 
wszystkie indeksy można od razu utworzyć dla danej tabeli, omijając nazwę 
konkretnego indeksu: 

UPDATE STATISTICS INVOICES 

W systemie Oracle statystykę indeksu uaktualniamy za pomocą komendy 

ANALYZE

. Zbiera ona statystyki dotyczące tabel, indeksów i klastrów (clusters). 

Poniższa konstrukcja służy do uaktualnienia statystyki jednego indeksu: 

ANALYZE INDEX CUSTOMER03 COMPUTE STATISTICS; 

Statystykę dla tabeli i jej indeksu można obliczyć następująco: 

ANALYZE TABLE CUSTOMER COMPUTE STATISTICS CASCADE; 

Statystykę dla całego klastra (łącznie z tabelami i indeksami) otwieramy zgodnie 
z poniższym zapisem: 

ANALYZE CLUSTER acctrecv COMPUTE STATISTICS CASCADE; 

Wyświetlanie planu optymalizatora zapytań 

Większość serwerów SQL umożliwia wyświetlenie planu działań serwera podczas 
wykonywania naszego zapytania. Plan w systemie InterBase wyświetlamy za 
pomocą edytora WISQL. W oknie dialogowym 

Basic ISQL Set Options

 z menu 

WISQL’s Session

 musimy w tym celu zaznaczyć opcję 

Display Query Plan

 

(wyświetlenie planu dla zapytania), zgodnie z rysunkiem 25.1. 

background image

738 

Część IV 

Powyższe czynności są równoważne komendzie 

SET PLAN ON

.  

Komenda 

SET STATS ON 

wyświetla informacje statystyczne o każdym 

zapytaniu po jego uruchomieniu. Nie należy jej mylić ze wspomnianą wcześniej 
komendą 

SET STAISTICS 

(która uaktualnia informacje dotyczące indeksów).  

W systemach Sybase i Microsoft, do wyświetlania planu optymalizatora służy 
komenda 

SET SHOWPLAN ON

. W połączeniu z opcją 

SET NOEXEC 

możemy za 

jej pomocą przejrzeć  ułożony przez serwer plan wykonania zapytania, bez 
konieczności jego uruchamiania.  

Informacje statystyczne związane z wykonaniem zapytania możemy również 
przeglądać poprzez komendę 

SET STATISTICS 

systemu SQL Server. 

Wyrażenie 

SET STATISTICS IO ON 

powoduje,  że system SQL Server 

wyświetli statystykę operacji wejścia/wyjścia dla każdego wykonywanego przezeń 
zapytania. Z kolei wyrażenie 

SET STATISTICS TIME ON 

spowoduje 

wyświetlenie informacji o 

uzależnieniach czasowych dla już wykonanego 

zapytania.  

Plan w systemie Oracle wyświetla się za pomocą komendy 

EXPLAIN PLAN 

PL/SQL

. Komendzie 

EXPLAIN PLAN

 przekazujemy wyrażenie, które chcemy 

wykonać, a Oracle kieruje opracowany przez siebie plan do specjalnej tablicy. 
Jeżeli wcześniej włączyliśmy optymalizację według kosztów, względny koszt 
zapytania również zostanie obliczony.  

Dzięki zapoznaniu się z planem, wygenerowanym przez optymalizator zapytań, 
lepiej zrozumiemy przyczyny takich, a nie innych zachowań zapytań i sami 
nauczymy się lepiej przeprowadzać optymalizację. Rysunek 25.2 ilustruje 
sytuację, w której plan optymalizatora uwidacznia nam, że dane zapytanie nie 
wykorzystuje indeksu.  

Słowo 

NATURAL 

w

 

planie wykonania wskazuje na to, że tablica jest właśnie 

przeszukiwana według naturalnego porządku wierszy (tzn. beż użycia indeksu).  

Przepiszmy teraz zapytanie tak, żeby indeks został w nim wykorzystany. Rysunek 
25.3. ilustruje nowe zapytanie i plan, wygenerowany dla niego przez system 
InterBase. 

 

Rysunek 25.1. 
Okno dialogowe 
Basic ISQL Set 
Options 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

739 

 

Przykład powyższy powinien uświadomić nam, jak ważna jest umiejętność 
przeglądania planów generowanych przez serwer. Dzięki uważnemu przejrzeniu 
planu wykonania zapytania możemy lepiej rozeznać się w możliwych sposobach 
jego optymalizacji.  

Plan wymuszony 

InterBase udostępnia rozszerzenie komendy 

SELECT

, które pozwala nam nakazać 

optymalizatorowi,  żeby zamiast opracowywania własnego planu, użył naszego. 
Oto odpowiednia konstrukcja składniowa: 

SELECT LastName, FirstName 
FROM CUSTOMER 
PLAN (CUSTOMER ORDER CUSTOMER03) 
ORDER BY LastName 

 

Rysunek 25.2. 
Plan wykonania 
zapytania bez 
użycia indeksu 

 

Rysunek 25.3. 
Plan wykonania 
zapytania 
z użyciem indeksu 

background image

740 

Część IV 

Na szczególną uwagę zasługuje klauzula 

PLAN

. Pierwszy argument informuje 

optymalizator, której tabeli związanej z zapytaniem dotyczy plan, a drugi - co jest 
naszym celem (w tym przypadku jest nim uporządkowanie tabeli). Ostatni 
argument informuje optymalizator, czego konkretnie chcemy użyć do 
przeprowadzenia naszego planu - w tym przypadku jest to indeks 

CUSTOMER03

.  

Możliwość taka okazuje się użyteczna w sytuacjach, gdy mamy podstawy sądzić, 
że optymalizator nie przyjął najlepszego strategii przy optymalizacji zapytania.  

W systemie SQL Server plan możemy wymusić na kilka sposobów. Pierwszy 
z nich  służy do wymuszenia zastosowania konkretnego indeksu. Odpowiednia 
konstrukcja składniowa wygląda następująco: 

SELECT LastName, FirstName 
FROM CUSTOMER 
ORDER BY LastName 

Zwróćmy uwagę, że cyfrę 

3

 ujęto w nawiasy. Informuje to optymalizator, że musi 

wykorzystać trzeci indeks utworzony dla tabeli CUSTOMER (dlatego też dobrze 
jest, gdy bazą nazwy indeksu jest nazwa tabeli, po której umieszczono liczbę; 
zapamiętanie numeru indeksu jest wówczas banalnie proste). 

Podanie liczby 0 wymusiłoby przeszukanie tabeli, a 

podanie liczby 1 - 

wykorzystanie wewnętrznego indeksu (clustered index) tabeli, jeżeli istnieje.  

Możemy również podać nazwę indeksu, np.: 

SELECT LastName, FirstName 
FROM CUSTOMER (INDEX = CUSTOMER03) 
ORDER BY LastName 

Drugim sposobem jest wymuszenie na optymalizatorze systemu SQL Server 
dołączenia tabel w konkretnej kolejności. Do tego celu służy komenda 

SET 

FORCEPLAN

. Włączenie opcji 

FORCEPLAN

 (dosł.  wymuszenie planu)  wymusi 

złączenie tabel w kolejności podanej w klauzuli 

FROM 

zapytania. Przeważnie 

dobrana przez optymalizator kolejność  łączenia jest najlepsza, ale nie zawsze. 
Jeżeli w wyniku wykonania komendy 

SHOWPLAN 

mamy podstawy sądzić,  że 

optymalizator nie dokonał trafnego wyboru, powinniśmy włączyć opcję 

FORCEPLAN

 i podać kolejność w klauzuli 

FROM

 naszego zapytania.  

Optymalizacja wydajności w sieci 

Niniejszy podrozdział zawiera szereg wskazówek dotyczących optymalizacji 
wydajności naszych aplikacji typu klient/serwer pod kątem komunikacji poprzez 
sieć. Szczególnie częstym  źródłem wąskich gardeł w komunikacji między 
klientami a serwerami są sieci rozległe (WAN).  

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

741 

UWAGA: 

Termin  sieci rozległe, czyli WAN  (Wide Area Network) dotyczy sieci 
komunikujących się poprzez linie cyfrowe o przepustowości 56Kb/s lub większej. 
Ponieważ nawet tak szybka linia jak T-1 charakteryzuje się przepustowością równą 
zaledwie jednej siódmej przepustowości sieci Ethernet (10Mb/s), kwestiom 
optymalizacji należy, w odniesieniu do WAN, poświęcić szczególnie dużo uwagi.  

Uwagi poniższe dotyczą optymalnego konfigurowania połączeń bazy danych 
poprzez sieć: 

„Szybsza sieć (np. 100 megabitów na sekundę) zwiększa wydajność aplikacji 

z niej korzystających, w tym aplikacji typu klient/serwer 

„Znaczący wpływ na wydajność mogą mieć szybkie karty sieciowe, dobra 

segmentacja sieci i inne, fizyczne aspekty systemu. Przypuszczalnie jest to 
oczywiste, ale czyż nigdy nie zdarzyło się nam przeoczyć rzeczy oczywistej? 

„Jeżeli protokół sieciowy obsługuje wielkie pakiety (oversized packets) (por. 

Large Internet Packet firmy Novell), możemy spróbować obniżyć liczbę 
pakietów przesyłanych przez sieć. Im mniej przekazujemy ich przez względnie 
wolną sieć WAN, tym lepiej 

„Sensowne może okazać się dynamiczne, a nie automatyczne tworzenie 

formularzy o drugorzędnym znaczeniu. Każde nowe połączenie sieciowe 
obciąża dodatkowo sieć. We względnie szybkich sieciach lokalnych jest to 
często niezauważalne. W przypadku połączeń WAN, jednakże, takie dodatkowe 
obciążenie może znacząco obniżyć wydajność. Musimy jednak wyważyć dwa 
przeciwstawne aspekty: z jednej strony powolność tworzenia i usuwania 
formularzy, z drugiej - redukcję obciążenia sieci, i zdecydować, co bardziej się 
nam opłaca  

„Tworząc aplikacje komunikujące się poprzez sieć rozległą, musimy dość 

sceptycznie traktować możliwości, jakie dają nam 

DBGrid

DBCrlGrid

 itp. 

Są to kontrolki odpowiednie dla sieci lokalnych, często jednak wymagają 
znacznie więcej danych, niż aplikacja rzeczywiście potrzebuje w danym 
przypadku. Powoduje to zbędne zajmowanie zasobów sieciowych 
i spowolnienie działania aplikacji  

„Należy minimalizować powiązania między głównymi, a 

szczegółowymi 

strukturami danych. Każda czynność wykonana na głównej tabeli generuje 
szereg zapytań dotyczących tabeli szczegółowej. Formularz z ich dużą ilością 
może bardzo zwiększyć ruch w sieci  

„Warto zastanowić się, czy w odniesieniu do danych tylko do odczytu (read-

only data), które znajdują się w tabelach o drugorzędnym znaczeniu, nie lepiej 
jest użyć komponentów typu 

Label,

 wraz ze statycznymi operacjami 

background image

742 

Część IV 

wyszukiwania i 

lokalizacji (Lookup/Locate), zamiast komponentów typu 

DBText

. Chociaż te ostatnie są łatwiejsze w konfiguracji, znacznie obciążają 

zasoby sieciowe. Zbyt duże obciążenie sieci powodowane przez aplikację 
oznacza w efekcie, że jest ona bezużyteczna  

„Rozważyć sensowność nadania wartości 

False

 

własności 

KeepConnection

 komponentu 

TDatabase

. Chociaż spowalnia to 

aplikację za każdym razem, gdy ponownie łączy się ona z serwerem, ma tę 
zaletę,  że zostawia użytkownikom systemu odległego maksymalnie dużo 
zasobów sieciowych. Ma to kluczowe znaczenie przy aplikacjach pracujących 
w sieci  rozległej. Inną zaletą takiej strategii jest minimalizacja połączeń 
użytkowników z naszym serwerem baz danych. Jeżeli dysponuje on tylko 
ograniczoną liczbą połączeń współbieżnych, praca w trybie usuwania połączeń 
nieaktywnych (

KeepConnections

:= 

False

) przyczyni się do zwiększenia 

liczby użytkowników, którzy mogą skorzystać z naszego systemu. 

„Przed wysłaniem z naszego serwera przez sieć dużych ilości danych do naszej 

aplikacji, należy je zblokować z procedurami pamiętanymi i zapytań. Innymi 
słowy, aplikacja nie powinna przetwarzać danych wiersz po wierszu - jest to 
zadanie procedur pamiętanych w serwerze. 

„Uaktualnienia należy grupować w postaci uaktualnień buforowanych, tak żeby 

nie musiały być używane po jednym na raz.  

„Włączyć buforowanie schematów BDE tak, żeby odbywało się ono w lokalnym 

komputerze. Buforowanie włącza się z poziomu BDE Configuration - przez 
nadanie parametrowi 

ENABLE SCHEMA CACHE

 wartości 

True

  

„Wykorzystywać udostępniane przez Delphi lokalne filtry do kwalifikowania 

małych zbiorów wynikowych na poziomie komputera lokalnego. Dzięki temu 
korzystanie z tych zbiorów nie będzie wymagało sieci  

„Używać udogodnień BatchMove środowiska Delphi - do przesuwania wielu 

wierszy między tabelami; niczego nie robić tylko po jednym wierszu na raz 

„Protokół TCP/IP czeka zwykle na zapełnienie pakietu przed jego wysłaniem. 

Jest to działanie domyślne. Może ono pogorszyć wydajność, kiedy rozmiar 
danych do wysłania przekracza aktualny rozmiar pakietu. Niewielka zmiana 
konfiguracji pozwala na wyłączenie tego opóźnienia w prawie każdym 
serwerze. Administrator systemu wie, jak to zrobić. Zwykle służy do tego opcja 
konfiguracji dla całego komputera, albo samego programu obsługi serwera baz 
danych. Np. platforma Sybase SQL Server w wersji System 11 udostępnia 
specjalny przełącznik sp_configure, który kontroluje składowanie pakietów 
TCP (TCP packet batching). W wersji System 10 realizuje się to poprzez flagę 
śledzenia 

(1610)

z poziomu linii komend serwera danych. W serwerach 

background image

 Rozdział 25 
 Optymalizacja 

wydajności w aplikacjach typu klient/serwer 

743 

pracujących w Systemie 10 opcję 

TCP_NODELAY 

włączamy w linii komend 

za pomocą 

-T1610

  

„W niektórych systemach operacyjnych precyzyjna optymalizacja ustawień 

przełączników, które dotyczą protokołu TCP/IP, może zwiększyć wydajność 
naszej aplikacji. Autor zamieścił poniżej listę znanych mu parametrów 
w systemie UNIX: 

ndd -set /dev/tcp tcp_rexmit_interval_max value 
ndd -set /dev/tcp tcp_conn_req_max value 
ndd -set /dev/tcp tcp_close_vait_interval value 
ndd -set /dev/tcp tcp_keep_alive_interval value 

Szczegóły składniowe są różne w 

różnych platformach, ale większość 

zaawansowanych serwerów umożliwia nam szeroką kontrolę protokołu TCP/IP. 
Jeżeli nasze aplikacje klienckie komunikują się z serwerem baz danych za 
pośrednictwem tego właśnie protokołu, użyteczność systemu możemy znacznie 
poprawić poprzez zoptymalizowanie jego działania