informatyka sql sztuka programowania stephane faroult ebook

background image

Wydawnictwo Helion
ul. Koœciuszki 1c
44-100 Gliwice
tel. 032 230 98 63

e-mail: helion@helion.pl

SQL. Sztuka
programowania

Wypowiedz wojnê niewydajnym bazom danych

• Projektowanie wydajnych baz danych
• Uwzglêdnianie kontekstu dzia³ania aplikacji bazodanowych
• Poprawa szybkoœci dzia³ania Ÿle zaprojektowanych systemów

Twoje bazy danych dzia³aj¹ zbyt wolno? Pora to zmieniæ! Wraz ze wzrostem wielkoœci
korporacyjnych baz danych czas dostêpu do nich ma coraz wiêksze znaczenie. Napisanie
poprawnie dzia³aj¹cego kodu w jêzyku SQL nie jest trudne, jednak tworzenie wydajnych
aplikacji bazodanowych jest prawdziw¹ sztuk¹. Jak mo¿esz zg³êbiæ jej tajniki i staæ siê
lepszym programist¹? Zdaniem autora tej ksi¹¿ki nauka wydajnej pracy z bazami danych
przypomina poznawanie zasad prowadzenia wojny, dlatego wzorem klasycznej pozycji
„Sztuka wojny” autorstwa Sun Tzu prowadzi Ciê on przez poszczególne etapy kampanii
przeciwko nieefektywnie zaprojektowanym i napisanym aplikacjom bazodanowym.

„SQL. Sztuka programowania” to praktyczny podrêcznik, dziêki któremu szybko
poszerzysz sw¹ wiedzê w zakresie efektywnego stosowania jêzyka SQL. Nauczysz siê
dbaæ o wydajnoœæ aplikacji ju¿ na etapie ich projektowania, a tak¿e myœleæ o pracy
z bazami danych w kategoriach procesów, wykraczaj¹c poza same zapytania jêzyka
SQL. Dowiesz siê, jak poprawnie u¿ywaæ indeksów oraz jak monitorowaæ szybkoœæ
dzia³ania bazy. Poznasz standardowe scenariusze zwiêkszania wydajnoœci, które
pozwol¹ Ci zastosowaæ sprawdzone fortele we w³asnych projektach oraz w bazach
zaprojektowanych przez innych programistów.

• Projektowanie pod k¹tem wydajnoœci
• Efektywne korzystanie z baz danych w programach
• Poprawne stosowanie indeksów
• Projektowanie optymalnych zapytañ SQL
• Praca z du¿ymi zbiorami danych
• Korzystanie ze struktur drzewiastych
• Monitorowanie wydajnoœci
• Obs³uga wspó³bie¿noœci
• Radzenie sobie z niewydajnymi projektami

Poznaj praktyczne techniki poprawy wydajnoœci baz danych

Autorzy: Stéphane Faroult, Peter Robson
T³umaczenie: Marek Pêtlicki
ISBN: 978-83-246-0895-9
Tytu³ orygina³u:

The Art of SQL

Format: B5, stron: 472

background image

S P I S T R E

¥ C I

Wst

¤p

7

1.

Plany strategiczne

15

Projektowanie baz danych pod k

 tem wydajnoĿci

2.

Prowadzenie wojny

51

Wydajne wykorzystanie baz danych

3.

Dzia

¨ania taktyczne

87

Indeksowanie

4.

Manewrowanie

113

Projektowanie zapyta

Ĭ SQL

5.

Ukszta

¨towanie terenu

151

Zrozumienie implementacji fizycznej

6.

Dziewi

¤ø zmiennych

179

Rozpoznawanie klasycznych wzorców SQL

7.

Odmiany taktyki

231

Obs

¨uga danych strategicznych

8.

Strategiczna si

¨a wojskowa

273

Rozpoznawanie trudnych sytuacji i post

¤powanie w nich

9.

Walka na wielu frontach

307

Wykorzystanie wspó

¨bieŞnoĿci

10.

Gromadzenie si

¨

337

Obs

¨uga duŞych iloĿci danych

11.

Fortele

381

Jak uratowa

ø czasy reakcji

12.

Zatrudnianie szpiegów

417

Monitorowanie wydajno

Ŀci

Ilustracje

451

O autorach

453

Skorowidz

455

background image

R O Z D Z I A

§ D R U G I

Prowadzenie wojny

Wydajne wykorzystanie baz danych

Il existe un petit nombre de principes fondamentaux de la guerre,

dont on ne saurait s’écarter sans danger, et dont l’application au contraire

a été presque en tous temps couronnée par le succès.

Istnieje niewielka liczba fundamentalnych zagadnie

Ĭ zwi zanych z prowadzeniem wojny,

których nie wolno lekcewa

Şyø: zaprawd¤, stosowanie si¤ do nich prawie niezawodnie

prowadzi do sukcesu.

— Genera

¨ Antoine-Henri de Jomini (1779 – 1869)

Zarys sztuki wojennej

background image

5 2

R O Z D Z I A

’ DRUGI

a

ĝdy, kto byï zaangaĝowany w proces przejĂcia projektu z fazy rozwoju

w faz

Ú produkcyjnÈ, przyzna z pewnoĂciÈ, ĝe prawie czuï tumult i wrzawÚ

bitwy. Bardzo cz

Ústo zdarza siÚ, ĝe na kilka tygodni przed sÈdnym dniem

przeprowadzone testy wydajno

Ăci ujawniajÈ smutny fakt: system nie

b

Údzie dziaïaï tak pïynnie, jak to zakïadano. Zaprasza siÚ ekspertów,

optymalizuje zapytania SQL, w kryzysowych burzach mózgów bior

È udziaï

administratorzy baz danych i systemów. W ko

ñcowym rozrachunku na

sprz

Úcie dwukrotnie bardziej kosztownym osiÈga siÚ wydajnoĂÊ jedynie

teoretycznie zbli

ĝonÈ do zakïadanej.

Cz

Ústo w zastÚpstwie dziaïañ strategicznych stosuje siÚ dziaïania taktyczne.

Strategia wymaga zastosowania architektury i modelu przystosowanych do
wymaga

ñ projektu. Podstawowych zasad stosowanych w czasie wojny jest

zaledwie kilka, ale zadziwiaj

Èco czÚsto bywajÈ one ignorowane. BïÚdy

w architekturze okazuj

È siÚ niezwykle kosztowne, a programista SQL-a

musi wkracza

Ê na pole bitwy dobrze przygotowany do walki, musi wiedzieÊ,

gdzie chce dotrze

Ê i którÈ drogÈ. W tym rozdziale przeanalizujemy

podstawowe cele zwi

ÚkszajÈce szanse na sukces w pisaniu programów

w sposób efektywny wykorzystuj

Ècych bazy danych.

Identyfikacja zapyta

ñ

Przez stulecia jedynym sposobem, w jaki genera

ï mógï ĂledziÊ losy bitwy,

by

ïa obserwacja oddziaïów na podstawie kolorów umundurowania

i niesionych przez nich proporców. Gdy jaki

Ă proces w Ărodowisku bazy

danych zu

ĝywa nadmiernÈ iloĂÊ mocy procesora, czÚsto istnieje moĝliwoĂÊ

zidentyfikowania zapytania SQL odpowiedzialnego za to zadanie.
Nierzadko jednak trudne bywa odkrycie tego, która cz

ÚĂÊ aplikacji

wywo

ïaïa problematyczne zapytanie, szczególnie w Ărodowisku, w którym

wykorzystywane s

È zapytania budowane w sposób dynamiczny. Mimo tego

ĝe wiele solidnych produktów wyposaĝonych jest w mechanizmy
monitoruj

Èce, czÚsto zdumiewajÈco trudno jest znaleěÊ odniesienie miÚdzy

zapytaniem SQL a

Ărodowiskiem, w którym ono dziaïa. Z tego powodu

dobrze jest nabra

Ê nawyku oznaczania programów i krytycznych moduïów

przez w

ïÈczanie komentarzy w kodzie SQL w taki sposób, aby ïatwo byïo

zidentyfikowa

Ê ěródïo kïopotów. Na przykïad:

/* REJESTRACJA KLIENTA */ select ...

K

background image

P R O W A D Z E N I E W O J N Y

5 3

Tego typu komentarze identyfikuj

Èce mogÈ byÊ pomocne w Ăledzeniu

b

ïÚdnie dziaïajÈcego kodu. MogÈ byÊ równieĝ pomocne przy okreĂlaniu

obci

Èĝenia serwera bazy danych powodowanego przez kaĝdÈ korzystajÈcÈ

z niego aplikacj

Ú, szczególnie w przypadku, gdy spodziewamy siÚ, ĝe

wprowadzane zmiany mog

È spowodowaÊ zwiÚkszenie obciÈĝenia, i musimy

oszacowa

Ê, czy posiadany sprzÚt ma szansÚ sprostaÊ wiÚkszym wymaganiom.

Niektóre produkty posiadaj

È specjalizowane mechanizmy rejestrujÈce,

które pozwalaj

È uniknÈÊ komentowania kaĝdego wyraĝenia. Na przykïad

pakiet

dbms_application_info

serwera Oracle pozwala oznakowa

Ê program

za pomoc

È 48-znakowej nazwy moduïu, 32-znakowej nazwy akcji

i 64-znakowego pola informacji o kliencie. Zawarto

ĂÊ tych pól jest

kontrolowana przez twórc

Ú aplikacji. W Ărodowisku Oracle moĝna uĝyÊ

tego pakietu do

Ăledzenia tego, jaka aplikacja wykorzystuje bazÚ danych

w danej chwili, jak równie

ĝ tego, co dana aplikacja robi. Do tego sïuĝÈ

dynamiczne perspektywy

V$

, za pomoc

È których moĝna ĂledziÊ stan

pami

Úci.

Identyfikowanie wyra

ĝeñ uïatwia Ăledzenie zaleĝnoĂci obciÈĝenia.

Trwa

ïe poïÈczenia do bazy danych

Nowe po

ïÈczenie do bazy danych tworzy siÚ szybko i ïatwo, lecz ta

prostota czasem przes

ïania fakt, ĝe szybkie, cykliczne wywoïania poïÈczeñ

wi

ÈĝÈ siÚ z konkretnym, niemaïym kosztem. Dlatego poïÈczenia z bazÈ

danych warto traktowa

Ê z rozwagÈ. Konsekwencje wywoïywania wielu

cyklicznych po

ïÈczeñ, byÊ moĝe ukrytych w ramach aplikacji, mogÈ byÊ

znacz

Èce, co zademonstruje kolejny przykïad.

Jaki

Ă czas temu trafiïem na aplikacjÚ, która przetwarzaïa duĝÈ liczbÚ

niewielkich plików tekstowych o rozmiarach do stu wierszy. Ka

ĝdy wiersz

zawiera

ï dane oraz identyfikator bazy danych, do której dane te miaïy byÊ

za

ïadowane. W tym przypadku byï wykorzystywany tylko jeden serwer

bazy danych, ale prezentowana zasada obowi

Èzuje równieĝ w przypadku

setek baz danych.

background image

5 4

R O Z D Z I A

’ DRUGI

Przetwarzanie ka

ĝdego z plików odbywaïo siÚ zgodnie z nastÚpujÈcÈ

procedur

È:

Otwarcie pliku
Aĝ do napotkania koñca pliku
Odczytaj wiersz
PoïÈcz siÚ z serwerem zdefiniowanym w treĂci
Wpisz dane
Zamknij poïÈczenie
Zamknij plik

Opisany proces dzia

ïaï zadowalajÈco z wyjÈtkiem przypadków, gdy do

za

ïadowania trafiaïa duĝa liczba niewielkich plików w odstÚpach czasu

przekraczaj

Ècych moĝliwoĂÊ ich przetworzenia przez aplikacjÚ.

To powodowa

ïo znaczny przyrost dziennika zalegïoĂci w bazie danych

(ang. backlog), który nast

Úpnie musiaï byÊ przetworzony przez bazÚ

danych, co zajmowa

ïo dodatkowy czas.

U

ĝytkownikowi bazy danych wyjaĂniïem przyczynÚ opóěnieñ, którÈ byïa

nadmierna liczba otwieranych i zamykanych po

ïÈczeñ. Jako demonstracjÚ

problemu przygotowa

ïem prosty program (napisany w C) emulujÈcy

aplikacj

Ú oraz proponowane przeze mnie rozwiÈzania. Wyniki dziaïania

demonstracji przedstawia tabela 2.1.

UWAGA

Program generuj

Ècy wyniki z tabeli 2.1 wykorzystywaï proste instrukcje

wstawiaj

Èce wiersze do tabel. Klientowi wspomniaïem równieĝ o technikach

bezpo

Ăredniego ïadowania danych do tabel, które dziaïajÈ jeszcze szybciej.

TABELA 2.1. Wyniki testu nawi

 zywania i koĬczenia po¨ czeĬ

Test

Wynik

Nawi

 zanie i zakoĬczenie po¨ czenia dla kaŞdego wstawianego

wiersza z pliku

7,4 wiersza na sekund

¤

Jedno po

¨ czenie, kaŞdy wiersz wstawiany indywidualnie

1681 wierszy na sekund

¤

Jedno po

¨ czenie, wiersze wstawiane w porcjach po 10

5914 wierszy na sekund

¤

Jedno po

¨ czenie, wiersze wstawiane w porcjach po 100

9190 wierszy na sekund

¤

Ta demonstracja pokaza

ïa, jak istotne jest, aby minimalizowaÊ liczbÚ

osobnych po

ïÈczeñ z bazÈ danych. Z tego powodu kluczowÈ rolÚ odgrywaïo

tu proste sprawdzenie, czy kolejne wprowadzenie danych ma odby

Ê siÚ do

tej samej bazy, do której po

ïÈczenie juĝ zostaïo otwarte. AnalizÚ moĝna by

background image

P R O W A D Z E N I E W O J N Y

5 5

poprowadzi

Ê dalej, poniewaĝ liczba moĝliwych baz danych byïa oczywiĂcie

ograniczona. Teoretycznie mo

ĝna byïo osiÈgnÈÊ dalszy zysk wydajnoĂci,

wykorzystuj

Èc tablicÚ uchwytów poïÈczeñ, po jednym dla kaĝdej z moĝliwych

baz danych, i nawi

ÈzujÈc poïÈczenie dopiero wtedy, gdy bÚdzie to konieczne.

W ten sposób wykorzystywanych by

ïoby maksymalnie tyle uchwytów, ile

istnieje baz danych. Jak wida

Ê w tabeli 2.1, prosta technika pojedynczego

po

ïÈczenia (lub minimalizacji liczby nawiÈzywanych poïÈczeñ) usprawnia

wydajno

ĂÊ do dwustu razy. I to wszystko dziÚki niewielkiej modyfikacji kodu.

Oczywi

Ăcie przy tej okazji mogïem zademonstrowaÊ równieĝ znaczÈce

korzy

Ăci wynikajÈce z ograniczenia liczby wywoïañ miÚdzy aplikacjÈ

a baz

È danych, wykorzystujÈc ïadowanie danych za pomocÈ tablic.

Przez wstawianie wielu wierszy w pojedynczej operacji ca

ïe zadanie

mo

ĝna przyspieszyÊ jeszcze piÚciokrotnie. Wyniki z tabeli 2.1 pokazujÈ

przyspieszenie o tysi

Èc dwieĂcie razy w stosunku do pierwotnej

wydajno

Ăci operacji.

Sk

Èd bierze siÚ tak znaczne przyspieszenie?

Przyczyn

È pierwszego przyspieszenia jest fakt, ĝe nawiÈzanie poïÈczenia

z baz

È danych jest operacjÈ „ciÚĝkÈ”, to znaczy wykorzystujÈcÈ duĝo

zasobów systemowych.

We wci

Èĝ popularnym Ărodowisku klient-serwer nawiÈzanie poïÈczenia

to prosta operacja, co powoduje,

ĝe niewiele osób ma ĂwiadomoĂÊ

kryj

Ècego siÚ za niÈ procesu. W pierwszym etapie klient musi nawiÈzaÊ

po

ïÈczenie z moduïem nasïuchujÈcym, wchodzÈcym w skïad serwera,

po czym modu

ï nasïuchujÈcy uruchamia osobny proces lub wÈtek

serwera bazy danych albo przekazuje (bezpo

Ărednio lub poĂrednio)

ĝÈdanie do istniejÈcego, oczekujÈcego procesu serwera.

Niezale

ĝnie od liczby operacji (wywoïywania nowych procesów lub

w

Ètków i wywoïywania obsïugi zapytania) system bazy danych musi

utworzy

Ê nowe Ărodowisko dla kaĝdej sesji, dziÚki czemu bÚdzie ona

mog

ïa ĂledziÊ realizowane w niej zadania. System obsïugi bazy danych

musi sprawdzi

Ê poprawnoĂÊ hasïa dla konta, za pomocÈ którego zostaïo

nawi

Èzane poïÈczenie i utworzona nowa sesja. System obsïugi bazy

danych cz

Ústo musi równieĝ wykonaÊ kod zwiÈzany z procedurÈ

zalogowania do bazy danych (zaimplementowany w postaci wyzwalacza).
To samo dotyczy równie

ĝ kodu inicjalizacyjnego niezbÚdnego do

dzia

ïania procedur osadzonych i pakietów. Na tym tle standardowy

background image

5 6

R O Z D Z I A

’ DRUGI

protokó

ï nawiÈzania poïÈczenia miÚdzy klientem a serwerem ma

niewielki wp

ïyw na powstaïe opóěnienia. Z powodu tej „zasoboĝernoĂci”

procesu nawi

Èzania poïÈczenia tak wielkie znaczenie z punktu widzenia

wydajno

Ăci majÈ rozmaite techniki pozwalajÈce na utrzymanie raz

nawi

Èzanego poïÈczenia z bazÈ danych, jak pule poïÈczeñ (ang.

connection pooling).

Druga przyczyna przyspieszenia wi

Èĝe siÚ ze zmniejszeniem liczby cykli

przesy

ïania danych miÚdzy aplikacjÈ a bazÈ danych (tzw. round-trips),

co, jak wida

Ê, równieĝ ma niebanalny udziaï w czasochïonnoĂci operacji.

Nawet w przypadku, gdy zosta

ïo nawiÈzane tylko jedno poïÈczenie

i jest ono wykorzystywane do realizacji wszystkich kolejnych operacji,
prze

ïÈczanie kontekstu miÚdzy programem a jÈdrem systemu obsïugi

bazy danych równie

ĝ ma wpïyw na dalsze opóěnienia. JeĂli zatem

system obs

ïugi bazy danych umoĝliwia wykorzystanie jakiegoĂ

mechanizmu

ïadowania danych wiÚkszymi porcjami (jak na przykïad

tablice), warto wzi

ÈÊ pod uwagÚ jego uĝycie. W rozwiÈzaniach, które

wykorzystuj

È rzeczywiste tablice, warto sprawdziÊ, jaki jest domyĂlny

rozmiar tablicy, i dostosowa

Ê go do potrzeb aplikacji. Kaĝdy rodzaj

operacji wykorzystuj

Ècych przetwarzanie pojedynczymi wierszami

wi

Èĝe siÚ oczywiĂcie z analogicznymi konsekwencjami, na co bÚdziemy

mieli sporo dowodów w tym rozdziale.

Po

ïÈczenia z bazami danych i przeïÈczenia kontekstu sÈ jak Chiñskie

Mury — im ich wi

Úcej, tym dïuĝej trwa przekazanie wiadomoĂci.

Strategia przed taktyk

È

To strategia definiuje taktyk

Ú, nie odwrotnie. Dobry programista nie

postrzega procesu w kategoriach drobnych kroczków, lecz z perspektywy
ostatecznego wyniku. Najefektywniejszy sposób uzyskania wyniku nie musi
wynika

Ê z procesów biznesowych, czÚsto sprawdza siÚ mniej bezpoĂrednie

podej

Ăcie. NastÚpny przykïad pokaĝe, w jaki sposób nadmierne skupienie

si

Ú na procesach proceduralnych moĝe odwróciÊ uwagÚ od najbardziej

efektywnych rozwi

Èzañ.

background image

P R O W A D Z E N I E W O J N Y

5 7

Kilka lat temu otrzyma

ïem zapytanie z proĂbÈ, abym spróbowaï je

zoptymalizowa

Ê. „Spróbowaï” to byïo sïowo kluczowe tego kontraktu.

Wcze

Ăniej odbyïy siÚ dwa podejĂcia do tej optymalizacji, pierwsze

podejmowane przez autorów, drugie przez eksperta Oracle. Mimo tych
prób ka

ĝde wywoïanie tego zapytania trwaïo okoïo dwudziestu minut,

co zdaniem autorów by

ïo nie do przyjÚcia.

Celem tej procedury by

ïo wyliczanie iloĂci materiaïów zamawianych przez

centraln

È jednostkÚ fabryki. Obliczenia wykorzystywaïy istniejÈce zapasy

i zamówienia pochodz

Èce z róĝnych ěródeï. Dane byïy odczytywane z kilku

identycznych tabel, a nast

Úpnie agregowane w jednej tabeli zbiorczej.

Procedura sk

ïadaïa siÚ z sekwencji wyraĝeñ o nastÚpujÈcej filozofii dziaïania:

najpierw dane ze wszystkich tabel by

ïy kopiowane do tabeli zbiorczej,

nast

Úpnie wywoïywane byïo zapytanie agregujÈce dane dotyczÈce

materia

ïów, nastÚpnie z tabeli byïy usuwane nadmiarowe dane niemajÈce

znaczenia dla procedury. Ta sekwencja by

ïa powtarzana dla kaĝdej tabeli

ěródïowej. ¿adne z zapytañ SQL nie byïo szczególnie skomplikowane,
ĝadnego z nich z osobna nie moĝna teĝ byïo okreĂliÊ jako szczególnie
nieefektywnego.

Wi

ÚkszÈ czÚĂÊ dnia zajÚïo mi zrozumienie procesu, co w koñcu zaowocowaïo

postawieniem pytania: „Dlaczego procedur

Ú wykonywano w kilku etapach?”.

Wystarczy

ïoby podzapytanie z operatorem unii (

UNION

), za pomoc

È którego

mo

ĝna poïÈczyÊ wszystkie wyniki z tabel ěródïowych. NastÚpnie

pojedyncza instrukcja

SELECT

pozwoli

ïaby za jednym zamachem zapeïniÊ

tabel

Ú wynikowÈ. Róĝnica w wydajnoĂci byïa imponujÈca: z dwudziestu

minut czas wykonania zadania skróci

ï siÚ do dwudziestu sekund.

Skuteczno

ĂÊ modyfikacji byïa tak zdumiewajÈca, ĝe sporÈ czÚĂÊ czasu

zaj

Úïa mi weryfikacja, czy aby nowa procedura generuje dokïadnie takie

same wyniki jak poprzednia.

Nie by

ïy tu potrzebne nadzwyczajne umiejÚtnoĂci, wystarczyïa umiejÚtnoĂÊ

my

Ălenia poza schematami. Poprzednie próby optymalizacji procesu

zako

ñczyïy siÚ niepowodzeniem, poniewaĝ byïy za bardzo zbliĝone do

sedna problemu. Cz

Ústo warto zdobyÊ siÚ na Ăwieĝe spojrzenie, zrobiÊ

krok wstecz, aby poszerzy

Ê perspektywÚ. Warto byïo zadaÊ dwa pytania:

„Co mamy dost

Úpnego przed wykonaniem procedury?” oraz „Jakie wyniki

chcemy uzyska

Ê z procedury?”. W poïÈczeniu ze Ăwieĝym spojrzeniem

odpowiedzi na te pytania doprowadzi

ïy do tak wielkiego udoskonalenia procesu.

background image

5 8

R O Z D Z I A

’ DRUGI

Spójrz na problem z szerszej perspektywy, zanim zag

ïÚbisz siÚ

w drobniejsze szczegó

ïy rozwiÈzania.

Najpierw definicja problemu,
potem jego rozwi

Èzanie

Brak wiedzy mo

ĝe byÊ niebezpieczny. CzÚsto zdarza siÚ, ĝe ludzie sïyszeli

lub czytali o nowych czy nietypowych technikach, w niektórych przypadkach
nawet do

ĂÊ ciekawych, które natychmiast chcieli wdroĝyÊ w ramach

rozwi

Èzywania swoich problemów. ProgramiĂci i architekci systemów

nader cz

Ústo zachwycajÈ siÚ tego typu „rozwiÈzaniami”, które w gruncie

rzeczy jedynie przyczyniaj

È siÚ do powstawania nowych problemów.

Na szczycie listy „gotowych rozwi

Èzañ” z reguïy moĝna znaleěÊ denormalizacjÚ.

Nie

Ăwiadomi praktycznych zagroĝeñ zwiÈzanych z modyfikacjÈ nadmiarowych

danych zwolennicy denormalizacji cz

Ústo sugerujÈ jÈ jako pierwsze podejĂcie

przy „optymalizacji” wydajno

Ăci. Niestety, zdarza siÚ to czÚsto na etapie

rozwoju aplikacji, gdy jeszcze nie jest za pó

ěno na zmiany projektu (lub

przynajmniej nauk

Ú konstruowania wydajnych zïÈczeñ tabel). Szczególnie

popularnym panaceum na problemy wydaje si

Ú specjalna forma denormalizacji,

jak

È jest zmaterializowana perspektywa. Zmaterializowane perspektywy

czasem okre

Ăla siÚ nazwÈ migawek (ang. snapshot). Jest to mniej

spektakularna nazwa, ale za to bardziej zbli

ĝona do nagiej prawdy o tym

zjawisku: chodzi bowiem ni mniej, ni wi

Úcej o kopie danych wykonania

w okre

Ălonym punkcie czasu. Nie chcÚ tu sugerowaÊ, ĝe nigdy nie zdarza siÚ,

ĝe od czasu do czasu, przyparty do muru, nie jestem zmuszony do zastosowania
kontrowersyjnych technik. Jak stwierdzi

ï Franz Kafka: „Logiki nie da siÚ

podwa

ĝyÊ, ale nie ma ona szans w obliczu czïowieka, który po prostu

próbuje przetrwa

Ê”.

Jednak przyt

ïaczajÈcÈ wiÚkszoĂÊ problemów daje siÚ rozwiÈzaÊ dziÚki

inteligentnemu zastosowaniu do

ĂÊ tradycyjnych technik. Warto nauczyÊ

si

Ú wyciÈgaÊ wszystko, co dobre, z takich prostych rozwiÈzañ. Gdy juĝ siÚ

je opanuje, cz

ïowiek uczy siÚ doceniaÊ ich ograniczenia. Dopiero potem

mo

ĝna próbowaÊ osÈdziÊ potencjalne zalety (o ile istniejÈ) nowych rozwiÈzañ

technicznych.

background image

P R O W A D Z E N I E W O J N Y

5 9

Wszystkie technologiczne rozwi

Èzania sÈ zaledwie Ărodkiem do osiÈgniÚcia

celu. Wielkim zagro

ĝeniem dla niedoĂwiadczonego programisty jest pociÈg

do najnowszej technologii, który szybko zamienia si

Ú w cel dla samego

siebie. A zagro

ĝenie jest tym wiÚksze w przypadku osób ciekawych,

obdarzonych du

ĝym entuzjazmem, o technicznym zaciÚciu.

Fundamenty s

È waĝniejsze od mody: naucz siÚ podstaw fachu, zanim

zaczniesz bawi

Ê siÚ nowinkami techniki.

Stabilny schemat bazy danych

Wykorzystanie j

Úzyka DDL (ang. data definition language) do tworzenia,

modyfikowania czy usuwania obiektów bazy danych jest bardzo nagann

È

praktyk

È, która powinna zostaÊ oficjalnie zakazana. Nie ma powodu, aby

dynamicznie tworzy

Ê, modyfikowaÊ i usuwaÊ obiekty bazy. Z wyjÈtkiem

partycji, o czym wspomn

Ú w rozdziale 5., oraz tabel tymczasowych,

z zaznaczeniem,

ĝe majÈ byÊ zadeklarowane jako tymczasowe w systemie

zarz

Èdzania bazami danych (istnieje jeszcze kilka waĝnych wyjÈtków

od tej regu

ïy, o czym wspomnÚ w rozdziale 10.).

Zastosowania j

Úzyka DDL wykorzystujÈ podstawowy sïownik danych bazy.

Ten s

ïownik jest równieĝ centralnym obiektem wszystkich operacji w bazie

danych, a wykorzystanie go prowadzi do wywo

ïywania globalnych blokad,

które powoduj

È znaczne konsekwencje w przypadku wydajnoĂci bazy.

Jedyn

È dopuszczalnÈ operacjÈ DDL jest przycinanie tabeli (

TRUNCATE

),

które jest bardzo szybk

È metodÈ usuwania danych z tabeli (ale naleĝy

pami

ÚtaÊ, ĝe w przypadku tej operacji nie mamy moĝliwoĂci jej wycofania

za pomoc

È instrukcji

ROLLBACK

!).

Tworzenie, modyfikacja i usuwanie obiektów baz danych to zadania
etapu projektowego, a nie codzienne operacje wykonywane przez
aplikacj

Ú klienckÈ.

background image

6 0

R O Z D Z I A

’ DRUGI

Operacje na rzeczywistych danych

Wielu programistów ch

Útnie posïuguje siÚ tymczasowymi tablicami

roboczymi, do których na przyk

ïad ïadujÈ dane do dalszego przetwarzania.

Tego typu podej

Ăcie z reguïy uznaje siÚ za niewïaĂciwe, poniewaĝ moĝe

ono prowadzi

Ê do zamkniÚcia percepcji w zakresie procesów biznesowych

i uniemo

ĝliwiÊ szersze spojrzenie. Naleĝy pamiÚtaÊ, ĝe tabele tymczasowe

nie daj

È moĝliwoĂci zastosowania pewnych zaawansowanych technik

dost

Úpnych w przypadku rzeczywistych tabel (czÚĂÊ z tego typu opcji

omówi

Ú w rozdziale 5.). Indeksowanie tabel tymczasowych (o ile w ogóle

jest dost

Úpne) moĝe na przykïad byÊ mniej optymalne. W wyniku tych

ogranicze

ñ zapytania wykorzystujÈce tabele tymczasowe mogÈ dziaïaÊ

mniej wydajnie od prawid

ïowo napisanych zapytañ na rzeczywistych

tabelach. Wad

È zastosowania tabel tymczasowych jest ponadto koniecznoĂÊ

wykonania dodatkowego zapytania wype

ïniajÈcego tabelÚ tymczasowÈ

danymi.

Nawet w przypadku, gdy zastosowanie tabel tymczasowych jest uzasadnione,
nie nale

ĝy nigdy wykorzystywaÊ w tym charakterze rzeczywistych tabel

udaj

Ècych tabele tymczasowe, szczególnie gdy liczba zapisywanych w nich

wierszy jest du

ĝa. Jeden z problemów leĝy tu w mechanizmie gromadzenia

statystyk: je

Ăli statystyki dotyczÈce tabel nie sÈ gromadzone w czasie

rzeczywistym, to system zarz

Èdzania bazÈ danych wykorzystuje do tego

chwile mniejszego obci

Èĝenia. Natura tabel roboczych polega na tym, ĝe

z regu

ïy w takich przestojach bywajÈ puste, co powoduje, ĝe optymalizator

uzyskuje zupe

ïnie bïÚdne informacje. W efekcie system podejmuje bïÚdne

decyzje w wyniku nieodpowiednich planów wykonawczych przygotowywanych
przez optymalizator zapyta

ñ, co prowadzi bezpoĂrednio do obniĝonej

wydajno

Ăci. JeĂli ktoĂ jest naprawdÚ zmuszony do uĝycia tabel

tymczasowych, powinien u

ĝywaÊ do tego celu tabel, które system

zarz

Èdzania bazami danych jest w stanie zidentyfikowaÊ jako tymczasowe.

Wykorzystanie tabel tymczasowych oznacza przepychanie danych do
mniej optymalnego zasobu.

background image

P R O W A D Z E N I E W O J N Y

6 1

Przetwarzanie zbiorów w SQL-u

SQL przetwarza dane w postaci zbiorów. W przypadku operacji
modyfikacji (

UPDATE

) lub usuwania danych (pod warunkiem,

ĝe te

modyfikacje nie s

È dokonywane na caïych tabelach) uĝytkownik musi

zdefiniowa

Ê zbiór wierszy, których dana modyfikacja dotyczy. W ten

sposób definiuje si

Ú pewnÈ granularnoĂÊ procesu, który moĝna okreĂliÊ

jako zgrubn

È, jeĂli zmiany dotyczÈ wiÚkszej liczby wierszy, lub drobnÈ,

gdy przetwarzamy jedynie kilka wierszy.

Ka

ĝda próba modyfikacji duĝej liczby wierszy tabeli, ale maïymi porcjami

jest z regu

ïy zïym pomysïem i zwykle okazuje siÚ bardzo niewydajna.

Takie podej

Ăcie jest dopuszczalne jedynie w przypadku, gdy na bazie danych

b

ÚdÈ dokonywane bardzo rozlegïe zmiany, które na czas transakcji mogÈ

spowodowa

Ê tymczasowe zajÚcie bardzo duĝej iloĂci zasobów oraz zajmujÈ

bardzo du

ĝo czasu w przypadku wycofania transakcji (

ROLLBACK

). Istniej

È

równie

ĝ opinie, ĝe w przypadku modyfikacji duĝych porcji danych naleĝy

w ramach kodu DML (ang. data manipulation code) umieszcza

Ê co jakiĂ

czas instrukcje zatwierdzaj

Èce zmiany (

COMMIT

). Tego typu podej

Ăcie moĝe

jednak nie sprawdzi

Ê siÚ w przypadku, gdy dane sÈ ïadowane z pliku

i procedura zostanie przerwana w trakcie. Z czysto praktycznego punktu
widzenia cz

Ústo o wiele proĂciej i szybciej jest wznowiÊ proces od poczÈtku,

ni

ĝ próbowaÊ zlokalizowaÊ miejsce w danych wejĂciowych, do którego

zosta

ïy juĝ zaïadowane, i od niego wznawiaÊ proces.

Natomiast bior

Èc pod uwagÚ rozmiar loga transakcji (ang. transaction log)

wykorzystywanego do wycofania zmian transakcji, równie

ĝ istniejÈ opinie,

ĝe fizyczna implementacja bazy danych powinna byÊ przygotowana
do przyjmowania zmian wykonywanych przez aplikacj

Ú, a aplikacja nie

powinna by

Ê zmuszona do omijania ograniczeñ fizycznej implementacji.

Je

Ăli wymagana iloĂÊ zasobów niezbÚdnych do zapisania loga transakcji jest

rzeczywi

Ăcie bardzo duĝa, zapewne naleĝy zastanowiÊ siÚ, czy czÚstotliwoĂÊ

dokonywania tego typu zmian jest odpowiednia do konstrukcji bazy.
Mo

ĝe siÚ bowiem okazaÊ, ĝe zmiana strategii z miesiÚcznych, gigantycznych

aktualizacji danych na kilkakrotnie mniejsze, tygodniowe, albo zupe

ïnie

niewielkie, dzienne modyfikacje spowoduje,

ĝe problem zupeïnie

przestanie istnie

Ê.

background image

6 2

R O Z D Z I A

’ DRUGI

Pracowite zapytania SQL

SQL nie jest j

Úzykiem proceduralnym. ChoÊ w zapytaniach SQL istnieje

mo

ĝliwoĂÊ zastosowania logiki jÚzyków proceduralnych, naleĝy traktowaÊ

je z rozwag

È. Problemy z rozróĝnieniem logiki proceduralnej od

przetwarzania deklaratywnego najlepiej wida

Ê w przypadkach, gdy ktoĂ

próbuje wydoby

Ê dane z bazy, dokonaÊ na nich modyfikacji, po czym

ponownie zapisa

Ê w bazie. Gdy program lub procedura dziaïajÈca

w ramach programu otrzyma dane wej

Ăciowe, czÚsto zdarza siÚ, ĝe sÈ one

wykorzystywane do odczytu innych danych z bazy, po czym nast

Úpuje

p

Útla lub inna forma logiki funkcyjnej (z reguïy pÚtla if...then...else)

zastosowana do wydobywania kolejnych danych z bazy. W wi

ÚkszoĂci

przypadków jest to efekt g

ïÚboko zakorzenionych nawyków lub sïabej

znajomo

Ăci SQL-a w poïÈczeniu z niewolniczym oddaniem w stosunku do

specyfikacji funkcjonalnej. Wiele stosunkowo skomplikowanych operacji
mo

ĝna wykonaÊ za pomocÈ pojedynczego zapytania w SQL-u. JeĂli

u

ĝytkownik poda wartoĂÊ, czÚsto moĝna podaÊ interesujÈcy go wynik bez

konieczno

Ăci rozbijania logiki na poszczególne instrukcje o niewielkim

zwi

Èzku z wynikiem koñcowym.

Istniej

È dwa gïówne powody, aby unikaÊ stosowania logiki proceduralnej

w SQL-u:

Ka

ĝdy dostÚp do bazy danych wiÈĝe siÚ z operacjami na wielu róĝnych

warstwach programowych, w

ïÈczajÈc w to operacje sieciowe.

Nawet w przypadku, gdy sie

Ê nie jest wykorzystywana, wchodzÈ w grÚ

operacje wymiany danych mi

Údzy procesami. WiÚcej operacji dostÚpu

oznacza wi

Úcej wywoïañ funkcji, wiÚcej przepustowoĂci, a co siÚ z tym

wi

Èĝe, dïuĝsze oczekiwanie na odpowiedě. Gdy tego typu wywoïania sÈ

cz

Ústo powtarzane, opóěnienia stajÈ siÚ wyraěnie zauwaĝalne.

„Proceduralny” znaczy,

ĝe wydajnoĂÊ i utrzymanie zaleĝÈ od programu,

nie od bazy danych.

Wi

ÚkszoĂÊ systemów baz danych do wykonywania operacji, jak na przykïad

z

ïÈczenia, wykorzystuje zaawansowane algorytmy przeksztaïcajÈce

zapytania w ich inne formy tak, aby wykonywa

ïy siÚ w wydajniejszy

sposób. Optymalizatory oparte na koszcie (ang. cost-based optimizers,
CBO) to skomplikowane modu

ïy, które pokonaïy dïugÈ drogÚ. Na

pocz

Ètku swojego istnienia mechanizmy tego typu byïy praktycznie

background image

P R O W A D Z E N I E W O J N Y

6 3

bezu

ĝyteczne, ale z czasem nabraïy dojrzaïoĂci i obecnie dajÈ doskonaïe

wyniki. Dobry mechanizm CBO bywa bardzo skuteczny w znajdowaniu
najbardziej odpowiedniego planu wykonania. Jednak

ĝe CBO analizuje

ka

ĝde zapytanie SQL, nic ponad to. WrzucajÈc jak najwiÚkszÈ liczbÚ

operacji w pojedyncze wyra

ĝenie, przerzucamy na bazÚ danych

odpowiedzialno

ĂÊ za znalezienie najbardziej optymalnego wykonania.

Dzi

Úki temu program moĝe wykorzystaÊ równieĝ przyszïe usprawnienia

w dzia

ïaniu silnika systemu zarzÈdzania bazÈ danych. W ten sposób

równie

ĝ przyszïe usprawnienia aplikacji sÈ czÚĂciowo przerzucane

na dostawc

Ú bazy danych.

Jak to zwykle bywa, równie

ĝ od zasady unikania logiki proceduralnej

istniej

È dobrze uzasadnione wyjÈtki. Dotyczy to sytuacji, gdy tego typu

podej

Ăcie pozwala na znaczÈce przyspieszenie w uzyskiwaniu wyników.

Rozbudowane, monstrualne zapytania SQL nie zawsze stanowi

È wzorzec

wydajno

Ăci. Jednak naleĝy pamiÚtaÊ, ĝe proceduralny kod sklejajÈcy

kolejno wykonywane zapytania SQL operuj

Èce na tych samych danych

(tabelach i wierszach) to dobry materia

ï na pojedyncze zapytanie SQL.

Mechanizm CBO analizuje pojedyncze zapytanie skonstruowane w zgodzie
z regu

ïami modelu relacyjnego jako jednÈ caïoĂÊ i jest w stanie opracowaÊ

efektywny sposób jego wykonania.

Jak najwi

Úcej pracy zrzucaj na optymalizator zapytañ, aby skorzystaÊ

z jego mo

ĝliwoĂci.

Maksymalne wykorzystanie dost

Úpu

do bazy danych

Gdy planujemy wizyt

Ú w wielu sklepach, w pierwszym kroku musimy

zdecydowa

Ê siÚ na to, co chcemy kupiÊ w kaĝdym z nich. DziÚki temu

mo

ĝna zaplanowaÊ trasÚ podróĝy i zminimalizowaÊ koniecznoĂÊ

przechodzenia mi

Údzy sklepami tam i z powrotem. Odwiedzamy pierwszy

sklep, dokonujemy zakupów, po czym odwiedzamy kolejny sklep.
To zdrowy rozs

Èdek, a jednak zasada obowiÈzujÈca w tym przykïadzie

wydaje si

Ú obca wielu praktycznym zastosowaniom baz danych.

background image

6 4

R O Z D Z I A

’ DRUGI

Gdy z pojedynczej tabeli chcemy odczyta

Ê kilka róĝnych informacji, nawet

niezbyt powi

Èzanych (co rzeczywiĂcie wydaje siÚ doĂÊ powszechnym

przypadkiem), nieoptymalne jest nawi

Èzywanie wielu poïÈczeñ, po jednym

dla odczytania ka

ĝdej porcji danych. Na przykïad nie naleĝy odczytywaÊ

pojedynczych kolumn, je

Ăli potrzebujemy wartoĂci z kilku z nich. Naleĝy

do tego wykorzysta

Ê pojedynczÈ operacjÚ. Niestety, dobre praktyki

programowania zorientowanego obiektowo z zasady pojedynczego odczytu
warto

Ăci atrybutów zrobiïy zaletÚ, nie wadÚ. Nie wolno jednak myliÊ metod

obiektowych z przetwarzaniem danych w bazach. Mieszanie tych poj

ÚÊ

nale

ĝy do najpowaĝniejszych bïÚdów, tabel nie wolno Ălepo traktowaÊ

jako klas, a kolumn jako atrybutów.

Ka

ĝdÈ wizytÚ w bazie danych wykorzystuj do wykonania jak

najwi

Úkszej iloĂci pracy.

Zbli

ĝenie do jÈdra systemu DBMS

Im bli

ĝej jÈdra systemu zarzÈdzania bazÈ danych moĝe dziaïaÊ kod, tym

b

Údzie dziaïaï wydajniej. To wïaĂnie sedno siïy baz danych. Na przykïad

niektóre systemy zarz

Èdzania bazami danych pozwalajÈ na rozszerzanie

swoich mo

ĝliwoĂci za pomocÈ nowych funkcji, które moĝna pisaÊ

w niskopoziomowych j

Úzykach programowania, jak na przykïad C. JÚzyki

niskiego poziomu operuj

Èce na wskaěnikach majÈ istotnÈ cechÚ negatywnÈ:

je

Ăli w procedurze zostanie popeïniony bïÈd, moĝna doprowadziÊ

do uszkodzenia danych w pami

Úci. Problem byïby powaĝny juĝ w przypadku,

gdyby z programu korzysta

ï tylko jeden uĝytkownik. Kïopot z bazami

danych polega jednak na tym,

ĝe mogÈ obsïugiwaÊ duĝÈ liczbÚ uĝytkowników.

W przypadku uszkodzenia danych w pami

Úci, moĝna uszkodziÊ dane

innego, zupe

ïnie „niewinnego” programu. W praktyce bywa tak, ĝe solidne

systemy zarz

Èdzania bazami danych wykonujÈ kod w pewnej izolacji

(technika okre

Ălana mianem piaskownicy, ang. sandbox), dziÚki czemu

w przypadku awarii procesu nie poci

Èga on za sobÈ caïej bazy danych.

Przyk

ïadem jest tu system Oracle, który do wymiany danych miÚdzy

procesami a baz

È danych wykorzystuje specjalny mechanizm komunikacji.

Ten proces jest podobny do

ïÈczy miÚdzy bazami danych pracujÈcych na

ĝnych serwerach. JeĂli zysk uzyskany z wydajnoĂci zewnÚtrznych funkcji

background image

P R O W A D Z E N I E W O J N Y

6 5

w C w stosunku do osadzonych procedur PL/SQL rekompensuje koszt
skonstruowania zewn

Útrznego Ărodowiska i przeïÈczeñ kontekstu, warto

wykorzysta

Ê funkcje zewnÚtrzne. Jednak nie warto ich stosowaÊ w przypadku,

gdy maj

È byÊ wykorzystywane do wywoïywania pojedynczo dla kaĝdego

wiersza tabeli. Jak to zwykle bywa, decyzja jest kwesti

È równowagi

i znajomo

Ăci wszystkich konsekwencji stosowania róĝnych strategii

rozwi

Èzywania tego samego problemu.

Je

Ăli funkcje majÈ byÊ jednak wykorzystane, warto w pierwszej kolejnoĂci

rozwa

ĝyÊ wykorzystanie funkcji standardowych, dostÚpnych w mechanizmie

zarz

Èdzania bazÈ danych. Nie chodzi tu wyïÈcznie o kwestiÚ unikania

„ponownego wynajdowania ko

ïa”, lecz przede wszystkim o to, ĝe funkcje

wbudowane dzia

ïajÈ znacznie bliĝej jÈdra systemu zarzÈdzania bazÈ danych

ni

ĝ zewnÚtrzny kod, a w zwiÈzku z tym sÈ bardziej wydajne.

Oto prosty przyk

ïad wykorzystania kodu SQL w bazie Oracle, za pomocÈ

którego zademonstruj

Ú wydajnoĂÊ uzyskanÈ dziÚki zastosowaniu funkcji

wbudowanych. Za

ïóĝmy, ĝe mamy dane tekstowe wprowadzone rÚcznie

przez u

ĝytkownika i ĝe te dane zawierajÈ zbÚdne ciÈgi znaku spacji.

Potrzebna jest nam funkcja, która zast

Èpi takie ciÈgi wielu znaków spacji

pojedynczym znakiem. Przyjmijmy,

ĝe nie bÚdziemy korzystaÊ z obsïugi

wyra

ĝeñ regularnych, dostÚpnej w Oracle Database 10g, a zamiast tego

napiszemy w

ïasnÈ funkcjÚ:

create or replace function squeeze1(p_string in varchar2)
return varchar2
is
v_string varchar2(512) := '';
c_char char(1);
n_len number := length(p_string);
i binary_integer := 1;
j binary_integer;
begin
while (i <= n_len)
loop
c_char := substr(p_string, i, 1);
v_string := v_string || c_char;
if (c_char = ' ')
then
j := i + 1;
while (substr(p_string || 'X', j, 1) = ' ')
loop
j := j + 1;

background image

6 6

R O Z D Z I A

’ DRUGI

end loop;
i := j;
else
i := i + 1;
end if;
end loop;
return v_string;
end;
/

Uwaga na marginesie: na ko

ñcu ciÈgu znaków dopisywany jest znak

X

, aby

unikn

ÈÊ porównania wartoĂci

j

z d

ïugoĂciÈ tego ciÈgu.

Istniej

È róĝne metody eliminacji ciÈgów spacji, w których moĝna wykorzystaÊ

funkcje udost

Úpniane w ramach bazy Oracle. Oto jedna z alternatyw:

create or replace function squeeze2(p_string in varchar2)
return varchar2
is
v_string varchar2(512) := p_string;
i binary_integer := l;
begin
i := instr(v_string, ' ');
while (i > 0)
loop
v_string := substr(v_string, 1, i)
|| ltrim(substr(v_string, i + l));
i := instr(v_string, ' ');
end loop;
return v_string;
end;
/

Trzecia z metod mo

ĝe byÊ nastÚpujÈca:

create or replace function squeeze3(p_string in varchar2)
return varchar2
is
v_string varchar2(512) := p_string;
len1 number;
len2 number;
begin
len1 := length(p_string);
v_string := replace(p_string, ' ', ' ');
len2 := length(v_string);
while (len2 < len1)
loop
len1 := len2;

background image

P R O W A D Z E N I E W O J N Y

6 7

v_string := replace(v_string, ' ',' ');
len2 := length(v_string);
end loop;
return v_string;
end;
/

Gdy te trzy alternatywne metody zostan

È przetestowane na prostym

przyk

ïadzie, kaĝda, zgodnie z oczekiwaniem, da dokïadnie takie same

wyniki i nie b

Údzie znaczÈcej róĝnicy w wydajnoĂci:

SQL> select squeeze1('azeryt hgfrdt r')
2 from dual
3 /
azeryt hgfrdt r

Elapsed: 00:00:00.00
SQL> select squeeze2('azeryt hgfrdt r')
2 from dual
3 /
azeryt hgfrdt r

Elapsed: 00:00:00.01
SQL> select squeeze3('azeryt hgfrdt r')
2 from dual
3 /
azeryt hgfrdt r

Elapsed: 00:00:00.00

Za

ïóĝmy jednak, ĝe operacja oczyszczania ciÈgów znaków z wielokrotnych

spacji b

Údzie wywoïywana tysiÈce razy dziennie. Poniĝszy kod moĝna

zastosowa

Ê do zaïadowania bazy danych danymi testowymi, za pomocÈ

których mo

ĝna w nieco bardziej realistycznych warunkach przetestowaÊ

wydajno

ĂÊ trzech przedstawionych wyĝej funkcji oczyszczajÈcych ciÈgi

znaków z wielokrotnych spacji:

create table squeezable(random_text varchar2(50))
/

declare
i binary_integer;
j binary_integer;
k binary_integer;
v_string varchar2(5O);

background image

6 8

R O Z D Z I A

’ DRUGI

begin
for i in 1 .. 10000
loop
j := dbms_random.value(1, 100);
v_string := dbms_random.string('U', 50);
while (j < length(v_string))
loop
k := dbms_random.value(l, 3);
v_string := substr(substr(v_string, 1, j) || rpad(' ', k)
|| substr(v_string, j + 1), 1, 50);
j := dbms_random.value(i, 100);
end loop;
insert into squeezable values(v_string);
end loop;
commit;
end;
/

Ten skrypt tworzy tabel

Ú testowÈ zïoĝonÈ z dziesiÚciu tysiÚcy wierszy

(to do

ĂÊ niewiele, biorÈc pod uwagÚ ĂredniÈ liczbÚ wywoïañ zapytañ SQL).

Test wywo

ïuje siÚ nastÚpujÈco:

select squeeze_func(random_text)
from squeezable;

Gdy wywo

ïywaïem ten test, wyïÈczyïem wyĂwietlanie wyników na

ekranie. Dzi

Úki temu upewniïem siÚ, ĝe czasy dziaïania algorytmów

oczyszczaj

Ècych wielokrotne spacje nie sÈ zafaïszowane przez czas

niezb

Údny na wyĂwietlenie wyników. Testy byïy wywoïane wielokrotnie,

aby upewni

Ê siÚ, ĝe nie wystÈpiï efekt przyspieszenia wykonania dziÚki

zbuforowaniu danych.

Czasy dzia

ïania tych algorytmów prezentuje tabela 2.2.

TABELA 2.2. Czas wykonania funkcji oczyszczaj

 cych ci gi spacji na danych testowych (10 000 wierszy)

Funkcja

Mechanizm

Czas wykonania

squeeze1

PL/SQL p

¤tla po elementach ci gu znaków

0,86 sekund

squeeze2

instr() + ltrim()

0,48 sekund

squeeze3

replace()

wywo

¨ana w p¤tli

0,39 sekund

Cho

Ê wszystkie z tych funkcji wykonujÈ siÚ dziesiÚÊ tysiÚcy razy w czasie

poni

ĝej jednej sekundy,

squeeze2()

jest 1,8 razy szybsza od

squeeze1()

,

a

squeeze3()

jest ponad 2,2 razy szybsza. Jak to si

Ú dzieje? Po prostu

background image

P R O W A D Z E N I E W O J N Y

6 9

PL/SQL nie jest tak „blisko j

Èdra”, jak funkcja SQL-a. Róĝnica wydajnoĂci

mo

ĝe wyglÈdaÊ na niewielkÈ w przypadku sporadycznego wywoïywania

funkcji, lecz w programie wsadowym lub na obci

Èĝonym serwerze OLTP

ĝnica moĝe juĝ byÊ powaĝna.

Kod uwielbia j

Èdro SQL-a — im jest bliĝej, tym jest gorÚtszy.

Robi

Ê tylko to, co niezbÚdne

Programi

Ăci czÚsto wykorzystujÈ instrukcjÚ

count(*)

do implementacji

testu istnienia. Z regu

ïy dochodzi do tego w celu implementacji

nast

ÚpujÈcej procedury:

JeĂli istniejÈ wiersze speïniajÈce warunek
Wykonaj na nich dziaïanie

Powy

ĝszy schemat jest implementowany za pomocÈ nastÚpujÈcego kodu:

select count(*)
into counter
from tabela
where <warunek>
if (counter > 0) then

Oczywi

Ăcie w 90% tego typu wywoïania instrukcji

count(*)

s

È zupeïnie

zb

Údne, dotyczy to równieĝ powyĝszego przykïadu. JeĂli jakieĂ dziaïanie

musi by

Ê wykonane na podzbiorze wierszy tabeli, dlaczego go po prostu nie

wykona

Ê od razu? JeĂli nie zostanie zmodyfikowany ani jeden wiersz, jaki

w tym problem? Nikomu nie stanie si

Ú ĝadna krzywda. Ponadto w sytuacji,

gdy operacja wykonywana w bazie danych sk

ïada siÚ z wielu zapytañ,

po wywo

ïaniu pierwszego z nich liczbÚ zmodyfikowanych wierszy moĝna

odczyta

Ê ze zmiennej systemowej (

@@ROWCOUNT

w Transact-SQL,

SOL%ROWCOUNT

w PL/SQL itp.), specjalnego pola SQL Communication Area (SQLCA)
w przypadku wykorzystania osadzonego SQL (embedded SQL) lub
za po

Ărednictwem specjalizowanych API, jak na przykïad funkcji

mysql_affected_rows()

j

Úzyka PHP. Liczba przetworzonych wierszy jest

czasem zwracana z funkcji, która wykonuje operacj

Ú w bazie danych, jak

metoda

executellpdate()

biblioteki JDBC. Zliczanie wierszy bardzo cz

Ústo

background image

7 0

R O Z D Z I A

’ DRUGI

nie s

ïuĝy niczemu oprócz zwiÚkszenia iloĂci pracy, jakÈ musi wykonaÊ baza,

poniewa

ĝ wiÈĝe siÚ ono z dwukrotnym przetworzeniem (a raczej: odczytaniem,

a potem przetworzeniem) tych samych danych.

Ponadto nie nale

ĝy zapominaÊ, ĝe jeĂli naszym celem jest aktualizacja

lub wstawienie wierszy (cz

Ústy przypadek: wiersze sÈ zliczane po to, by

stwierdzi

Ê istnienie klucza), niektóre systemy zarzÈdzania bazami danych

oferuj

È specjalne instrukcje (jak MERGE w Oracle 9i Database), które

dzia

ïajÈ bardziej wydajnie niĝ w przypadku zastosowania osobnych zapytañ

zliczaj

Ècych.

Nie ma potrzeby, aby jawnie kodowa

Ê to, co baza danych wykonuje

w sposób niejawny.

Instrukcje SQL-a
odwzorowuj

È logikÚ biznesowÈ

Wi

ÚkszoĂÊ systemów baz danych udostÚpnia mechanizmy monitorujÈce,

za pomoc

È których moĝna sprawdzaÊ stan wykonywanych aktualnie

instrukcji, a nawet

ĂledziÊ liczbÚ ich wywoïañ. Przy okazji moĝna

u

ĂwiadomiÊ sobie liczbÚ przetwarzanych jednoczeĂnie „jednostek

biznesowych”: zamówie

ñ lub innych zgïoszeñ, klientów z wystawionymi

fakturami lub dowolnych innych zdarze

ñ istotnych z punktu widzenia

biznesowego. Mo

ĝna zweryfikowaÊ, czy istnieje sensowne (a nawet

absolutnie precyzyjne) prze

ïoĝenie miÚdzy dwoma klasami aktywnoĂci.

Innymi s

ïowy: czy dla zadanej liczby klientów liczba odwoïañ do bazy

danych za ka

ĝdym razem jest taka sama? JeĂli zapytanie do tabeli klientów

jest wywo

ïywane dwadzieĂcia razy czÚĂciej, niĝ wskazywaïaby na to liczba

przetwarzanych klientów, to z pewno

ĂciÈ wskazuje na jakiĂ bïÈd. Taka

sytuacja sugerowa

ïaby, ĝe zamiast jednorazowego odczytu danych z tabeli,

program dokonuje du

ĝej iloĂci zbÚdnych odczytów tych samych danych

z tabeli.

Nale

ĝy sprawdziÊ, czy dziaïania w bazie danych sÈ spójne z realizowanymi

funkcjami biznesowymi aplikacji.

background image

P R O W A D Z E N I E W O J N Y

7 1

Programowanie logiki w zapytaniach

Istnieje kilka sposobów implementacji logiki biznesowej w aplikacji
wykorzystuj

Ècej bazÚ danych. CzÚĂÊ logiki proceduralnej moĝna

zaimplementowa

Ê w ramach instrukcji SQL-a (choÊ ze swej natury SQL

mówi o tym, co nale

ĝy zrobiÊ, a nie jak). Nawet w przypadku dobrej

integracji SQL-a w innych j

Úzykach programowania zaleca siÚ, aby jak

najwi

Úcej logiki biznesowej ujmowaÊ w SQL-u. Taka strategia pozwala

na uzyskanie wy

ĝszej wydajnoĂci przetwarzania danych niĝ w przypadku

implementacji logiki w aplikacji. J

Úzyki proceduralne to takie, w których

mo

ĝna definiowaÊ iteracje (pÚtle) oraz stosowaÊ logikÚ warunkowÈ

(konstrukcje if...then...else). SQL nie potrzebuje p

Útli, poniewaĝ ze swojej

natury operuje na zbiorach danych. Potrzebuje jedynie mo

ĝliwoĂci

okre

Ălania warunków wykonania okreĂlonych dziaïañ.

Logika warunkowa wymaga obs

ïugi dwóch elementów: IF i ELSE. Obsïuga

IF w SQL-u to prosta sprawa: warunek

WHERE

zapewnia dok

ïadnie takÈ

semantyk

Ú. Natomiast z obsïugÈ logiki ELSE jest pewien problem. Na

przyk

ïad mamy za zadanie pobraÊ z tabeli zbiór wierszy, po czym wykonaÊ

ĝne typy operacji, w zaleĝnoĂci od typów zbiorów. Fragment tej logiki

mo

ĝna zasymulowaÊ z uĝyciem wyraĝenia

CASE

(Oracle od dawna obs

ïuguje

odpowiednik tej operacji w postaci funkcji

decode()

1

). Mi

Údzy innymi

mo

ĝna modyfikowaÊ w locie wartoĂci zwracane w ramach zbioru wynikowego

w zale

ĝnoĂci od speïnienia okreĂlonych warunków. W pseudokodzie moĝna

to zapisa

Ê nastÚpujÈco

2

:

CASE
WHEN warunek THEN <zwrócenie okreĂlonej wartoĂci>
WHEN warunek THEN <zwrócenie innej wartoĂci>
WHEN warunek THEN <zwrócenie jeszcze innej wartoĂci>
ELSE <wartoĂÊ domyĂlna>
END

Porównywanie warto

Ăci liczbowych i dat to operacje intuicyjne. W przypadku

ci

Ègów znaków mogÈ byÊ przydatne funkcje znakowe, jak

greatest()

czy

least()

znane z Oracle, czy

strcmp()

z MySQL-a. Czasem te

ĝ bywa

1

Funkcja

decode()

jest nieco bardziej „surowa” w stosunku do konstrukcji

CASE

.

Do uzyskania tych samych efektów mo

ĝe byÊ konieczne wykorzystanie dodatkowych

funkcji, na przyk

ïad

sign()

.

2

Istniej

È dwa warianty konstrukcji

CASE

, przedstawiona wersja jest bardziej zaawansowana.

background image

7 2

R O Z D Z I A

’ DRUGI

mo

ĝliwe zastosowanie pewnej formy logiki w instrukcjach za pomocÈ

wielokrotnych i logicznych operacji wstawiania do tabel oraz za pomoc

È

wstawiania

ïÈczÈcego

3

(

merge insert

). Nie nale

ĝy unikaÊ takich instrukcji,

o ile s

È dostÚpne w posiadanym systemie zarzÈdzania bazami danych.

Innymi s

ïowy, polecenia SQL-a moĝna wyposaĝyÊ w duĝÈ iloĂÊ elementów

kontrolnych. W przypadku pojedynczej operacji korzy

ĂÊ z tych mechanizmów

by

Ê moĝe nie jest wielka, lecz z zastosowaniem instrukcji

CASE

i wielu

instrukcji wykonywanych warunkowo jest ju

ĝ o co walczyÊ.

O ile to mo

ĝliwe, warto implementowaÊ logikÚ aplikacji

w zapytaniach SQL zamiast w wykorzystuj

Ècej je aplikacji.

Jednoczesne wielokrotne modyfikacje

Moje g

ïówne zaïoĝenie w tym podrozdziale opiera siÚ na stwierdzeniu,

ĝe kolejne modyfikacje danych w pojedynczej tabeli sÈ dopuszczalne pod
warunkiem

ĝe dotyczÈ rozïÈcznych zbiorów wierszy. W przeciwnym razie

nale

ĝy ïÈczyÊ je w ramach pojedynczego zapytania. Oto przykïad

z rzeczywistej aplikacji

4

:

update tbo_invoice_extractor
set pga_status = 0
where pga_status in (1, 3)
and inv_type = 0;
update tbo_invoice_extractor
set rd_status = 0
where rd_status in (1, 3)
and inv_type = 0;

W tej samej tabeli dokonywane s

È dwie kolejne operacje modyfikujÈce.

Czy te same wiersze b

ÚdÈ wykorzystywane dwukrotnie? Nie ma

mo

ĝliwoĂci, aby to stwierdziÊ. Zasadniczym pytaniem jest tu jednak, jak

wydajne s

È kryteria wyszukiwania? Atrybuty o nazwach

type

(typ) lub

status

z du

ĝym prawdopodobieñstwem gwarantujÈ sïabÈ dystrybucjÚ

warto

Ăci. Jest zatem caïkiem moĝliwe, ĝe najefektywniejszym sposobem

odczytu tych danych b

Údzie peïne, sekwencyjne przeszukiwanie tabeli.

3

Dost

Úpny na przykïad w Oracle od wersji 9.2.

4

Nazwy tabel zosta

ïy zmienione.

background image

P R O W A D Z E N I E W O J N Y

7 3

Mo

ĝe teĝ byÊ tak, ĝe jedno z zapytañ wykorzysta indeks, a drugie bÚdzie

wymaga

ïo peïnego przeszukiwania. W najkorzystniejszym przypadku

obydwa zapytania skorzystaj

È z wydajnego indeksu. Niezaleĝnie jednak

od tego, nie mamy prawie nic do stracenia, aby nie spróbowa

Ê poïÈczyÊ

obydwu zapyta

ñ w jedno:

update tbo_invoice_extractor
set pga_status = (case pga_status
when 1 then 0
when 3 then 0
else pga_status
end),
rd_status = (case rd_status
when 1 then 0
when 3 then 0
else rd_status
end)
where (pga_status in (1, 3)
or rd_status in (1, 3))
and inv_type = 0;

Istnieje prawdopodobie

ñstwo wystÈpienia niewielkiego narzutu

spowodowanego aktualizacj

È kolumn o wartoĂci juĝ przez nie posiadanej.

Jednak w wi

ÚkszoĂci przypadków jedna zïoĝona aktualizacja danych jest

o wiele szybsza ni

ĝ skïadowe wywoïane osobno. Warto zauwaĝyÊ

zastosowanie logiki warunkowej z u

ĝyciem instrukcji

CASE

. Dzi

Úki temu

przetworzone zostan

È tylko te wiersze, które speïniajÈ kryteria, niezaleĝnie

od tego, jak wiele kryteriów b

Údzie zastosowanych w zapytaniu.

Operacje modyfikuj

Èce warto wykonywaÊ w pojedynczej, zïoĝonej

operacji, aby zminimalizowa

Ê wielokrotne odczyty tej samej tabeli.

Ostro

ĝne wykorzystanie

funkcji u

ĝytkownika

Gdy w zapytaniu jest wykorzystana funkcja u

ĝytkownika, istnieje

mo

ĝliwoĂÊ, ĝe bÚdzie wywoïywana wielokrotnie. JeĂli funkcja wystÚpuje

w li

Ăcie

SELECT

, b

Údzie wywoïywana dla kaĝdego zwróconego wiersza. JeĂli

wyst

Èpi w instrukcji

WHERE

, b

Údzie wywoïywana dla kaĝdego sprawdzonego

background image

7 4

R O Z D Z I A

’ DRUGI

wiersza, który spe

ïnia kryteria sprawdzone wczeĂniej. To moĝe oznaczaÊ

bardzo wiele wywo

ïañ w przypadku, gdy wczeĂniej sprawdzane kryteria

nie s

È bardzo mocno selektywne.

Warto si

Ú zastanowiÊ, co siÚ stanie, gdy taka funkcja wywoïuje inne

zapytanie. To zapytanie b

Údzie wywoïywane przy kaĝdym wywoïaniu

funkcji. W praktyce jej wynik b

Údzie taki sam jak w przypadku wywoïania

podzapytania, z t

È róĝnicÈ, ĝe w przypadku zapytania ukrytego w funkcji

optymalizator nie ma mo

ĝliwoĂci lepszego zoptymalizowania zapytania

g

ïównego. Co wiÚcej, procedura osadzona jest wykonywana na osobnej

warstwie abstrakcji w stosunku do silnika SQL, wi

Úc bÚdzie dziaïaÊ mniej

wydajnie ni

ĝ bezpoĂrednie podzapytanie.

Zaprezentuj

Ú przykïad demonstrujÈcy zagroĝenia wynikajÈce z ukrywania

kodu SQL w funkcjach u

ĝytkownika. Weěmy pod uwagÚ tabelÚ

flights

opisuj

ÈcÈ loty linii lotniczych. Tabela ta zawiera kolumny:

flight_number

,

departure_time

,

arrival_time

i

iata_airport_codes

5

. S

ïownik kodów (okoïo

dziewi

ÚÊ tysiÚcy pozycji) jest zapisany w osobnej tabeli zawierajÈcej nazwÚ

miasta (lub lotniska, je

Ăli w jednym mieĂcie znajduje siÚ kilka lotnisk),

nazw

Ú kraju itp. OczywiĂcie kaĝda informacja o locie wyĂwietlana

u

ĝytkownikom powinna zawieraÊ nazwÚ miasta i lotniska docelowego

zamiast nic niemówi

Ècego kodu IATA.

W tym miejscu trafiamy na jedn

È ze sprzecznoĂci w inĝynierii nowoczesnego

oprogramowania. Do „dobrych praktyk” programowania zaliczana jest
mi

Údzy innymi modularnoĂÊ, polegajÈca w uproszczeniu na opracowaniu

kilku odosobnionych warstw logiki. Ta zasada sprawdza si

Ú doskonale

w ogólnym przypadku, lecz w kontek

Ăcie baz danych, w których kod

stanowi element wspólny mi

Údzy programistÈ a bazÈ danych, potrzeba

zastosowania modularno

Ăci kodu jest znacznie mniej wyraěna. Zastosujmy

jednak zasad

Ú modularnoĂci, tworzÈc niewielkÈ funkcjÚ zwracajÈcÈ peïnÈ

nazw

Ú lotniska na podstawie kodu IATA:

create or replace function airport_city(iata_code in char)
return varchar2
is
city_name varchar2(50);
begin
select city

5

IATA: International Air Transport Association.

background image

P R O W A D Z E N I E W O J N Y

7 5

into city_naine
from iata_airport_codes
where code = iata_code;
return(city_name);
end;
/

Dla czytelników niezaznajomionych ze sk

ïadniÈ Oracle: wywoïanie

trunc(sysdate

) zwraca dzisiejsz

È datÚ, godzinÚ 00:00, arytmetyka dat jest

oparta na dniach. Warunek dotycz

Ècy czasów odlotu odnosi siÚ zatem do

czasów mi

Údzy 8:30 a 16:00 dnia dzisiejszego. Zapytania wykorzystujÈce

funkcj

Ú

airport_city()

mog

È byÊ bardzo proste, na przykïad:

select flight_number,
to_char(departure_time, 'HH24:MI') DEPARTURE,
airport_city(arrival) "TO"
from flights
where departure_time between trunc(sysdate) + 17/48
and trunc(sysdate) + 16/24
order by departure_time
/

To zapytanie wykonuje si

Ú z zadowalajÈcÈ prÚdkoĂciÈ. Z zastosowaniem

losowej próbki na mojej maszynie siedemdziesi

Èt siedem wierszy jest

zwracanych w czasie 0,18 sekundy (

Ărednia z kilku wywoïañ). Taka

wydajno

ĂÊ jest do przyjÚcia. Statystyki informujÈ jednak, ĝe podczas

wywo

ïania zostaïy odczytane trzysta trzy bloki w piÚÊdziesiÚciu trzech

operacjach odczytu z dysku. A nale

ĝy pamiÚtaÊ, ĝe ta funkcja jest

wywo

ïywana rekurencyjnie dla kaĝdego wiersza.

Alternatyw

È dla funkcji pobierajÈcej dane z tabeli (sïownika) moĝe byÊ

z

ïÈczenie tabel. W tym przypadku zapytanie nieco siÚ skomplikuje:

select f.flight_number,
to_char(f.departure_time, 'HH24:MI') DEPARTURE,
a.city "TO"
from flights f,
iata_airport_codes a
where a.code = f.arrival
and departure_time between trunc(sysdate) + 17/48
and trunc(sysdate) + 16/24
order by departure_time
/

background image

7 6

R O Z D Z I A

’ DRUGI

To zapytanie wykonuje si

Ú w czasie 0,05 sekundy (te same statystyki, ale

nie mamy do czynienia z rekurencyjnymi wywo

ïaniami). Takie oszczÚdnoĂci

mog

È wydaÊ siÚ niewiele warte — trzykrotne przyspieszenie zapytania w wersji

nieoptymalnej trwaj

Ècego uïamek sekundy. Jednak doĂÊ powszechne jest,

ĝe w rozbudowanych systemach (miÚdzy innymi na lotniskach) niektóre
zapytania s

È wywoïywane setki tysiÚcy razy dziennie. Zaïóĝmy, ĝe nasze

zapytanie musi by

Ê wywoïywane piÚÊdziesiÈt tysiÚcy razy dziennie. Gdy

zostanie u

ĝyta wersja zapytania wykorzystujÈca funkcjÚ, caïkowity czas

wykonania tych zapyta

ñ wyniesie okoïo dwie godziny i trzydzieĂci minut.

W przypadku z

ïÈczenia bÚdÈ to czterdzieĂci dwie minuty. Oznacza to

usprawnienie rz

Údu 300%, co w Ărodowiskach o duĝej liczbie zapytañ

oznacza znacz

Èce przyspieszenie, które moĝe przekïadaÊ siÚ na konkretne

oszcz

ÚdnoĂci finansowe. Bardzo czÚsto zastosowanie funkcji powoduje

niespodziewany spadek wydajno

Ăci zapytania. Co wiÚcej, wydïuĝenie czasu

wykonania zapyta

ñ powoduje, ĝe mniej uĝytkowników jest w stanie

korzysta

Ê z bazy danych jednoczeĂnie, o czym wiÚcej piszÚ w rozdziale 9.

Kod funkcji u

ĝytkownika nie jest poddawany analizie

optymalizatora.

Oszcz

Údny SQL

Do

Ăwiadczony programista baz danych zawsze stara siÚ wykonaÊ jak

najwi

Úcej pracy za pomocÈ jak najmniejszej liczby instrukcji SQL-a.

Klasyczny programista natomiast stara si

Ú dostosowaÊ swój program

do ustalonego schematu funkcyjnego. Na przyk

ïad:

-- Odczyt pocz

ątku okresu ksiĊgowego

select closure_date
into dtPerSta
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period='1' || to_char(Param_dtAcc,'MM');
-- Odczyt ko

Ĕca okresu na podstawie daty początku

select closure_date
into dtPerClosure
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period='9' || to_char(Param_dtAcc,'MM');

background image

P R O W A D Z E N I E W O J N Y

7 7

To jest przyk

ïad kodu o bardzo niskiej jakoĂci, mimo tego ĝe szybkoĂÊ jego

wykonania jest zadowalaj

Èca. Niestety, taka jakoĂÊ jest typowa dla wiÚkszoĂci

kodu, z którym musz

È mierzyÊ siÚ specjaliĂci od optymalizacji. Dlaczego

dane s

È odczytywane z zastosowaniem dwóch osobnych zapytañ? Ten

przyk

ïad byï uruchamiany na bazie Oracle, w której ïatwo zaimplementowaÊ

zapytanie zapisuj

Èce odpowiednie wartoĂci w tabeli wynikowej. Wystarczy

odpowiednio zastosowa

Ê instrukcjÚ

ORDER BY

na kolumnie

rslt_period

:

select closure_date
bulk collect into dtPerStaArray
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),
'9' || to_char(Param_dtAcc,'MM'))
order by rslt_period;

Dwie odczytane daty s

È zapisywane odpowiednio w pierwszej i drugiej

komórce macierzy. Operacja

bulk collect

jest specyficzna dla j

Úzyka

PL/SQL, lecz w pozosta

ïych jÚzykach obsïugujÈcych pobieranie danych

do macierzy obowi

Èzuje podobna zasada.

Warto zauwa

ĝyÊ, ĝe macierz nie jest tu niezbÚdna, a te dwie wartoĂci moĝna

pobra

Ê do zmiennych skalarnych, wystarczy zastosowaÊ nastÚpujÈcÈ sztuczkÚ

6

:

select max(decode(substr(rslt_period, 1, 1), -- sprawdzenie pierwszego znaku
'1', closure_date,
-- je

Ğli to '1', zwracamy datĊ

to_date('14/10/1066', 'DD/MM/YYYY'))),
-- w przeciwnym razie
max(decode(substr(rslt_period, 1, 1),
'9', closuredate, -- o t

Ċ datĊ chodzi

to_date('14/10/1066', 'DD/MM/YYYY'))),
into dtPerSta, dtPerClosure
from tperrslt
where fiscal_year=to_char(Param_dtAcc, 'YYYY')
and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),
'9' || to_char(Param_dtAcc,'MM'));

6

Funkcja

decode()

baz danych Oracle dzia

ïa jak instrukcja

CASE

. Dane porównywane

podaje si

Ú w pierwszym argumencie. JeĂli wartoĂÊ jest równa drugiemu argumentowi,

zwracany jest trzeci. Je

Ăli nie zostanie podany piÈty argument, w takim przypadku

czwarty jest traktowany jako warto

ĂÊ

ELSE

; w przeciwnym razie, je

Ăli pierwszy

argument jest równy czwartemu, zwracany jest pi

Èty i tak dalej w odpowiednich

parach warto

Ăci.

background image

7 8

R O Z D Z I A

’ DRUGI

W tym przyk

ïadzie wynik bÚdzie dwuwierszowy, a oczekujemy wyniku

jednowierszowego zawieraj

Ècego dwie kolumny (tak, jak w przykïadzie

z macierz

È). Dokonamy tego, sprawdzajÈc za kaĝdym razem wartoĂÊ

w kolumnie rozró

ĝniajÈcej wartoĂci z kaĝdego wiersza, czyli

rslt_period

.

Je

Ăli odnaleziony wiersz jest tym, którego szukamy, zwracana jest odpowiednia

data. W przeciwnym razie zwracana jest dowolna data (w tym przypadku
data bitwy pod Hastings), znacznie starsza (z punktu widzenia porównania
mniejsza”) od jakiejkolwiek daty w tej tabeli. Wybieraj

Èc maksimum,

mamy pewno

ĂÊ, ĝe otrzymamy odpowiedniÈ datÚ. Ten trik jest bardzo

praktyczny i mo

ĝna go z powodzeniem stosowaÊ do danych znakowych

lub liczbowych. Wi

Úcej tego typu technik omówiÚ w rozdziale 11.

SQL jest j

Úzykiem deklaratywnym, zatem naleĝy zachowaÊ dystans

do proceduralno

Ăci zastosowañ biznesowych.

Ofensywne kodowanie w SQL-u

Programistom cz

Ústo doradza siÚ programowanie defensywne polegajÈce

mi

Údzy innymi na sprawdzaniu poprawnoĂci wszystkich parametrów przed

ich zastosowaniem w wywo

ïaniu. Przy korzystaniu z baz danych wiÚksze

zalety ma jednak kodowanie ofensywne, polegaj

Èce na wykonywaniu kilku

dzia

ïañ równolegle.

Dobrym przyk

ïadem jest mechanizm obsïugi kontroli poprawnoĂci

polegaj

Ècy na wykonywaniu serii sprawdzeñ i zaprojektowany w ten

sposób,

ĝe w przypadku wystÈpienia choÊ jednego wyniku negatywnego

wywo

ïywany jest wyjÈtek. Zaïóĝmy, ĝe mamy przetworzyÊ pïatnoĂÊ kartÈ

p

ïatniczÈ. Kontrola takiej transakcji skïada siÚ z kilku etapów. Naleĝy

sprawdzi

Ê, ĝe poprawny jest identyfikator klienta i numer karty oraz ĝe sÈ

prawid

ïowo ze sobÈ powiÈzane. Naleĝy równieĝ zweryfikowaÊ datÚ

wa

ĝnoĂci karty. No i oczywiĂcie bieĝÈcy zakup nie moĝe spowodowaÊ

przekroczenia limitu karty. Gdy wszystkie testy zako

ñczÈ siÚ pomyĂlnie,

mo

ĝe zostaÊ przeprowadzona operacja obciÈĝenia konta karty.

Niedo

Ăwiadczony programista mógïby napisaÊ coĂ takiego:

select count(*)
from customers
where customer_id = id_klienta

background image

P R O W A D Z E N I E W O J N Y

7 9

W tym miejscu nast

Úpuje sprawdzenie wyniku, a jeĂli wynik jest pomyĂlny,

nast

Úpuje wywoïanie:

select card_num, expiry_date, credit_limit
from accounts
where customer_id = id_klienta

Tutaj równie

ĝ nastÈpi sprawdzenie wyniku, po którym (w przypadku

powodzenia) wywo

ïywana jest transakcja finansowa.

Do

Ăwiadczony programista zapewne napisze to nieco inaczej

(zak

ïadajÈc, ĝe

today()

to funkcja zwracaj

Èca bieĝÈcÈ datÚ):

update accounts
set balance = balance – wielkosc_zamowienia
where balance >= wielkosc_zamowienia
and credit_limit >= wielkosc_zamowienia
and expiry_date > today()
and customer_id = id_klienta
and card_num = numer_karty

Tutaj nast

Úpuje sprawdzenie liczby zmodyfikowanych wierszy. JeĂli jest to

zero, przyczyn

Ú takiej sytuacji moĝna sprawdziÊ za pomocÈ jednego zapytania:

select c.customer_id, a.card_num, a.expiry_date,
a.creditlimit, a.balance
from customers c
left outer join accounts a
on a.customer_id = c.customer_id
and a.cardnum = numer_karty
where c.customer_id = id_klienta

Je

Ăli zapytanie nie zwróci ĝadnego wiersza, oznacza to, ĝe wartoĂÊ

customer_id

jest b

ïÚdna, jeĂli w wyniku

card_num

jest NULL, oznacza to,

ĝe numer karty jest bïÚdny itd. Jednak w wiÚkszoĂci przypadków to drugie
zapytanie nie b

Údzie nawet uruchomione.

UWAGA

Warto zwróci

Ê uwagÚ na wywoïanie

count(*)

w pierwszym fragmencie kodu

niedo

Ăwiadczonego programisty. To doskonaïa ilustracja bïÚdnego uĝycia funkcji

count(*)

do sprawdzenia, czy w tabeli istniej

È pozycje speïniajÈce warunek.

Zasadnicz

È cechÈ programowania ofensywnego jest opieranie swoich zaïoĝeñ

na rozs

Èdnym prawdopodobieñstwie. Na przykïad nie ma wiÚkszego sensu,

by sprawdza

Ê istnienie klienta. JeĂli nie istnieje, w bazie danych nie

znajdzie si

Ú ĝaden dotyczÈcy go rekord (zatem w wyniku wywoïania

zapytania bez wcze

Ăniejszej kontroli i tak nie zostanÈ zmodyfikowane

background image

8 0

R O Z D Z I A

’ DRUGI

ĝadne dane)! Zakïadamy, ĝe wszystko zakoñczy siÚ powodzeniem, a nawet
je

Ăli tak siÚ nie stanie, przygotowujemy mechanizm ratujÈcy nas z opresji

w tym jednym punkcie — i tylko w tym jednym. Co interesuj

Èce, tego

typu strategia przypomina nieco „optymistyczn

È kontrolÚ wspóïdzielenia”

zastosowan

È w niektórych bazach danych. Chodzi o to, ĝe zaïoĝono z góry,

i

ĝ z duĝym prawdopodobieñstwem nie bÚdzie sytuacji konfliktu dostÚpu

do danych, a je

Ăli jednak siÚ to zdarzy, dopiero wówczas uruchamiane sÈ

stosowne konstrukcje kontrolne. W wyniku zastosowania tej strategii
wydajno

ĂÊ systemu jest znacznie wyĝsza niĝ w przypadku systemów

stosuj

Ècych strategiÚ pesymistycznÈ.

Nale

ĝy kodowaÊ w oparciu o rachunek prawdopodobieñstwa.

Zak

ïada siÚ, ĝe najprawdopodobniej wszystko zakoñczy siÚ

pomy

Ălnie, a dopiero w przypadku niepowodzenia uruchamia siÚ

plany awaryjne.

¥wiadome uĝycie wyjÈtków

Mi

Údzy odwagÈ a zapalczywoĂciÈ róĝnica jest doĂÊ subtelna. Gdy zalecam

stosowanie agresywnych metod kodowania, nie sugeruj

Ú bynajmniej szarĝy

w stylu Lekkiej Brygady pod Ba

ïakïawÈ

7

. Programowanie z u

ĝyciem wyjÈtków

równie

ĝ moĝe byÊ konsekwencjÈ brawury, gdy dumni programiĂci decydujÈ siÚ

„i

ĂÊ na caïoĂÊ”. MajÈ bowiem przeĂwiadczenie, ĝe testy i moĝliwoĂÊ obsïugi

wyj

Ètków bÚdÈ ich tarczÈ w tym boju. No tak, odwaĝni umierajÈ mïodo!

Jak sugeruje nazwa, wyj

Ètki to zdarzenia wystÚpujÈce w niecodziennych

sytuacjach. W programowaniu z u

ĝyciem baz danych nie wszystkie wyjÈtki

wykorzystuj

È te same zasoby systemowe. Naleĝy poznaÊ te uwarunkowania,

aby korzysta

Ê z wyjÈtków w sposób inteligentny. Moĝna wyróĝniÊ dobre

wyj

Ètki, wywoïywane, zanim zostaje wykonane dziaïanie, oraz zïe wyjÈtki,

które s

È wywoïywane dopiero po fakcie wyrzÈdzenia powaĝnych zniszczeñ.

7

Podczas Wojny Krymskiej, w 1854 roku, odby

ïa siÚ bitwa miÚdzy wojskami Anglii,

Francji i Turcji a si

ïami Rosji. W wyniku nieprecyzyjnego rozkazu oraz osobistych

animozji mi

Údzy niektórymi z dowódców siï sprzymierzonych doszïo do szarĝy ponad

sze

Ăciuset ĝoïnierzy kawalerii brytyjskiej wprost na bateriÚ rosyjskiej artylerii. Na skutek

starcia zgin

Úïo okoïo stu dwudziestu kawalerzystów oraz poïowa koni, bez jakiegokolwiek

dobrego rezultatu. Odwaga ludzi zosta

ïa wkrótce wysïawiona przez wiersz Tennysona,

a potem w kilku filmach hollywoodzkich, dzi

Úki czemu zwykïa gïupota jednej militarnej

decyzji obróci

ïa siÚ w mit.

background image

P R O W A D Z E N I E W O J N Y

8 1

Zapytanie wykorzystuj

Èce klucz gïówny, które nie znajdzie ĝadnych

wierszy, wykorzystuje niewiele zasobów — sytuacja jest identyfikowana
ju

ĝ na etapie przeszukiwania indeksu. JeĂli jednak w celu stwierdzenia, ĝe

dane spe

ïniajÈce warunek nie wystÚpujÈ w tabeli, zapytanie nie moĝe uĝyÊ

indeksu, zachodzi konieczno

ĂÊ dokonania peïnego przeszukiwania tabeli

(full scan). W przypadku wielkich tabel czas potrzebny do odczytu
sekwencyjnego w systemie dzia

ïajÈcym w danym monecie na granicy

swojej wydajno

Ăci moĝna potraktowaÊ jako czynnik katastrofalny.

Niektóre wyj

Ètki sÈ szczególnie kosztowne, nawet przy najbardziej

sprzyjaj

Ècych okolicznoĂciach. Weěmy na przykïad wykrywanie

duplikatów. W jaki sposób w bazie danych jest obs

ïugiwany mechanizm

unikalno

Ăci? Prawie zawsze sïuĝy do tego unikalny indeks i gdy wystÈpi

próba wprowadzenia do tabeli warto

Ăci zawierajÈcej klucz wystÚpujÈcy juĝ

w indeksie, zadzia

ïa mechanizm zabezpieczajÈcy przed zduplikowaniem

klucza, co efektywnie zablokuje zapis duplikatu. Jednak

ĝe zanim nastÈpi

próba zapisu indeksu (weryfikacji duplikatu), w tabeli musi zosta

Ê

fizycznie zapisana odpowiednia warto

ĂÊ (do procedury indeksujÈcej

przesy

ïany jest fizyczny adres wiersza w tabeli). Z tego wynika, ĝe

naruszenie ograniczenia unikalno

Ăci klucza nastÚpuje po fakcie zapisu

w tabeli danych, które musz

È byÊ wycofane, czemu dodatkowo towarzyszy

komunikat informuj

Ècy o wystÈpieniu bïÚdu. Wszystkie te operacje wiÈĝÈ

si

Ú z okreĂlonym kosztem czasowym. NajwiÚkszym jednak grzechem jest

podejmowanie samodzielnych prób dzia

ïania na poziomie wyjÈtków.

W takim przypadku przejmujemy od systemu zadanie obs

ïugi operacji

na poziomie wierszy, nie ca

ïych zbiorów danych, czyli sprzeciwiamy siÚ

fundamentalnej koncepcji relacyjnego modelu danych. Konsekwencj

È

wyst

Úpowania czÚstych naruszeñ ograniczeñ w bazie bÚdzie w takim

przypadku stopniowa degradacja jej wydajno

Ăci.

Przyjrzyjmy si

Ú przykïadowi opartemu na bazie Oracle. Zaïóĝmy, ĝe

pracujemy nad integracj

È systemów informatycznych dwóch poïÈczonych firm.

Adres e-mail zosta

ï ustandaryzowany w postaci wzorca

<InicjaïNazwisko>

i ma zawiera

Ê co najwyĝej dwanaĂcie znaków, wszystkie spacje i znaki

specjalne s

È zastÚpowane znakami podkreĂlenia

8

.

8

Przyk

ïad nie uwzglÚdnia obsïugi polskich znaków diakrytycznych, niedozwolonych

w adresach e-mail — przyp.red.

background image

8 2

R O Z D Z I A

’ DRUGI

Za

ïóĝmy, ĝe nowÈ tabelÚ pracowników naleĝy wypeïniÊ trzema tysiÈcami

wierszy z tabeli

employees_old

. Chcemy te

ĝ, ĝeby kaĝdy pracownik

posiada

ï unikalny adres e-mail. Z tego powodu musimy zastosowaÊ

okre

ĂlonÈ zasadÚ nazewnictwa: Jan Kowalski bÚdzie miaï e-mail o postaci

jkowalski

, a Józef Kowalski (

ĝadnego pokrewieñstwa)

jkowalski2

itd.

W naszych danych testowych znajdziemy trzydzie

Ăci trzy potencjalne

pozycje konfliktowe, co przy próbie

ïadowania danych da nastÚpujÈcy efekt:

SQL> insert into employees(emp_num, emp_name,
emp_firstname, emp_email)
2 select emp_num,
3 emp_name,
4 emp_firstname,
5 substr(substr(EMP_FIRSTNAME, 1, 1)
6 ||translate(EMP_NAME, ' ''', '_'), 1, 12)
7 from employees_old;

insert into employees(emp_num, emp_name, emp_firstname, emp_email)
*
ERROR at line 1:
ORA-0000l: unique constraint (EMP_EMAIL_UQ) violated

Elapsed: 00:00:00.85

Trzydzie

Ăci trzy duplikaty ze zbioru trzech tysiÚcy to trochÚ powyĝej 1%,

by

Ê moĝe zatem warto byïoby obsïuĝyÊ te 99%, a elementy problemowe

obs

ïuĝyÊ z uĝyciem wyjÈtków? W koñcu 1% danych nie powinien

powodowa

Ê znacznego obciÈĝenia bazy w wyniku procedury obsïugi

wyj

Ètków. Poniĝej kod realizujÈcy ten optymistyczny scenariusz:

SQL> declare
2 v_counter varchar2(l2);
3 b_ok boolean;
4 n_counter number;
5 cursor c is select emp_num,
6 emp_name,
7 emp_firstname
8 from employees_old;
9 begin
10 for rec in c
11 loop
12 begin
13 insert into employees(emp_num, emp_name,
14 emp_firstname, emp_email)

background image

Czytaj dalej...

P R O W A D Z E N I E W O J N Y

8 3

15 values (rec.emp_num,
16 rec.emp_name,
17 rec.emp_firstname,
18 substr(substr(rec.emp_firstname, 1, 1)
19 ||translate(rec.emp_name, ' ''', ' '), 1, 12));
20 exception
21 when dup_val_on_index then
22 b_ok := FALSE;
23 n_counter := 1;
24 begin
25 v counter := ltrim(to_char(n_counter));
26 insert into employees(emp_num, emp_name,
27 emp_firstname, emp_email)
28 values (rec.emp_num,
29 rec.emp_name,
30 rec.emp_firstname,
31 substr(substr(rec.emp_firstname, 1, 1)
32 ||translate(rec.emp_name, ' ''', '__'), 1,
33 12 - length(v_counter)) || v_counter);
34 b_ok : = TRUE;
35 exception
36 when dup_val_on_index then
37 n_counter := n_counter + 1;
38 end;
39 end;
40 end loop;
41 end;
40 /

PL/SOL procedure successfully completed.
Elapsed: 00:00:18.41

Jaki jest jednak rzeczywisty koszt obs

ïugi wyjÈtków? Gdyby ten sam test

przeprowadzi

Ê na danych pozbawionych duplikatów, okaĝe siÚ, ĝe koszt

rzeczywistej obs

ïugi wyjÈtków (ich wystÈpieñ) jest pomijalny. Procedura

wywo

ïana na danych z duplikatami dziaïa okoïo osiemnaĂcie sekund,

podobnie jak na danych bez duplikatów. Jednak gdy wykonamy ten test
(dane bez duplikatów) na naszej oryginalnej procedurze nieobs

ïugujÈcej

wyj

Ètków (

insert...select

), zauwa

ĝymy, ĝe wykona siÚ znacznie szybciej

od p

Útli. PrzeïÈczenie siÚ w tryb „wiersz po wierszu” powoduje okoïo

50-procentowy narzut czasu przetwarzania. Czy w takim razie mo

ĝliwe

jest unikni

Úcie tego trybu? To kwestia tego, czy zdecydujemy siÚ na rezygnacjÚ

z mechanizmu obs

ïugi wyjÈtków, który to wïaĂnie zmusiï nas do obsïugi

danych w trybie wierszowym.


Wyszukiwarka

Podobne podstrony:
informatyka html5 zaawansowane programowanie peter lubbers ebook
informatyka jquery poradnik programisty wlodzimierz gajda ebook
informatyka rails zaawansowane programowanie brad ediger ebook
SQL Sztuka programowania
informatyka ios 5 podrecznik programisty erica sadun ebook
SQL Sztuka programowania sqlszp
informatyka html5 podrecznik programisty chuck hudson ebook
informatyka pear programowanie w php stephan schmidt ebook
informatyka sztuka kodowania sekrety wielkich programistow peter seibel ebook
informatyka piekny kod tajemnice mistrzow programowania andy oram ebook
informatyka php5 bezpieczne programowanie leksykon kieszonkowy jacek ross ebook
informatyka asp net mvc 4 programowanie jess chadwick ebook
informatyka java zadania z programowania z przykladowymi rozwiazaniami miroslaw j kubiak ebook
(Ebook Pdf) Informix 4Gl And Informix Sql
informatyka inkscape podstawowa obsluga programu krzysztof ciesla ebook

więcej podobnych podstron