26 rozdzial 25 PK6QXDXOCBYMLC6T Nieznany (2)

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.
Z
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

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

z

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
z

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

=

0

OnClick

=

Button1Click

end

object DBGrid3: TDBGrid

Left

=

72

Top

=

248

Width

=

497

Height

=

113

DataSource

=

DataSource3

TabOrder

=

1

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

=

2

OnClick

=

Button2Click

end

object Query1: TQuery

DatabaseName

=

‘dbthread1’

SessionName

=

‘Ses1’

SQL.Strings

=

(

‘select

*

from

EMPLOYEE’)

Left

=

16

Top

=

8

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


Wyszukiwarka

Podobne podstrony:
26 rozdzial 25 nealxkacg62juqze Nieznany
08 Rozdzial 25 26
Rozdział 25-26, Dni Mroku 1 - Nocny wędrowiec
25 rozdzial 24 4LC3HTZN4LOZNAK7 Nieznany (2)
25 26 ROZ w sprawie wzorow w Nieznany (2)
25 rozdzial 24 yuqrc7z5sklazoiq Nieznany (2)
08 Rozdzial 25 26
ROZDZIAŁ 25, 26, 27
antropomotoryka 26 2004 id 6611 Nieznany (2)
05 rozdzial 04 nzig3du5fdy5tkt5 Nieznany (2)
28 rozdzial 27 vmxgkzibmm3xcof4 Nieznany (2)
geometria zadania 1 25 aksonome Nieznany (3)
26 33 id 31365 Nieznany (2)
26 Prowadzenie analiz wskazniko Nieznany (2)
22 Rozdzial 21 KP4Q5YBIEV5DBSVC Nieznany (2)
09 08 Rozdzielnice budowlane RB Nieznany (2)
17 rozdzial 16 fq3zy7m2bu2oan6t Nieznany (2)

więcej podobnych podstron