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 znalezć 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,
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 wskaznikó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 wskaznika uwagę skupiamy przede wszystkim na
ułatwieniu aplikacji dostępu do serwera, optymalizacji samego serwera
i umożliwieniu współbieżności.
odpowiedzi na zapytanie (query response time) - wydajność systemu
Czas
można też mierzyć według czasu, przez jaki konkretne zapytanie zostaje
wykonanie. Przy stosowaniu tego wskaznika 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.
Rozdział 25
717
Optymalizacja wydajności w aplikacjach typu klient/serwer
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.
realizacji współbieżności (concurrency) - tworzonego przez nas
Sposób
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. wskaznik 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 wskaznika 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.
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.
Rozdział 25
719
Optymalizacja wydajności w aplikacjach typu klient/serwer
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.
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.
Rozdział 25
721
Optymalizacja wydajności w aplikacjach typu klient/serwer
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.
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
Rozdział 25
723
Optymalizacja wydajności w aplikacjach typu klient/serwer
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.
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.
Rozdział 25
725
Optymalizacja wydajności w aplikacjach typu klient/serwer
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:
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,
Rozdział 25
727
Optymalizacja wydajności w aplikacjach typu klient/serwer
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ż
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ózniał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 zró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 zródłowy modułu thrdex00.pas - pierwszego
dwóch modułów przykładowego programu thrdex,
z
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;
Rozdział 25
729
Optymalizacja wydajności w aplikacjach typu klient/serwer
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
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
z
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
Rozdział 25
731
Optymalizacja wydajności w aplikacjach typu klient/serwer
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
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
od zródłowy modułu thrdex01.pas programu
Listing 25.4. K
thrdex, wykorzystującego wielowątkowość
unit thrdex01;
Rozdział 25
733
Optymalizacja wydajności w aplikacjach typu klient/serwer
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;
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
zró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
Rozdział 25
735
Optymalizacja wydajności w aplikacjach typu klient/serwer
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
736 Część IV
asynchroniczną komunikację wejścia/wyjścia, sprawdzmy, 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.
Rozdział 25
737
Optymalizacja wydajności w aplikacjach typu klient/serwer
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.
738 Część IV
Rysunek 25.1.
Okno dialogowe
Basic ISQL Set
Options
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.
Rozdział 25
739
Optymalizacja wydajności w aplikacjach typu klient/serwer
Rysunek 25.2.
Plan wykonania
zapytania bez
użycia indeksu
Rysunek 25.3.
Plan wykonania
zapytania
z użyciem indeksu
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
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 zródłem wąskich gardeł w komunikacji między
klientami a serwerami są sieci rozległe (WAN).
Rozdział 25
741
Optymalizacja wydajności w aplikacjach typu klient/serwer
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ć:
sieć (np. 100 megabitów na sekundę) zwiększa wydajność aplikacji
Szybsza
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?
protokół sieciowy obsługuje wielkie pakiety (oversized packets) (por.
Jeżeli
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
minimalizować powiązania między głównymi, a szczegółowymi
Należy
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
zastanowić się, czy w odniesieniu do danych tylko do odczytu (read-
Warto
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
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
udogodnień BatchMove środowiska Delphi - do przesuwania wielu
Używać
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óznienia 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
Rozdział 25
743
Optymalizacja wydajności w aplikacjach typu klient/serwer
pracujących w Systemie 10 opcję TCP_NODELAY włączamy w linii komend
za pomocÄ… -T1610
niektórych systemach operacyjnych precyzyjna optymalizacja ustawień
W
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:
08 Rozdzial 25 26rozdział 25 Prześwięty Asziata Szyjemasz, z Góry posłany na ZiemięRozdział 2526 RozdziałBestia Zachowuje się Źle Shelly Laurenston Rozdział 25Rozdział 25Rozdział 25Chińska Gnoza rozdział 25 I III 1Rozdział 2526 25 Wrzesień 1999 Śmiercionośny bumerangTom I rozdziały 26 32więcej podobnych podstron