24 rozdzial 23 ahuayqd5v2qhx4vw Nieznany (2)

background image

Rozdział 23

Sterowanie współbieżnością

Termin sterowanie współbieżnością (ang. concurrency control) odnosi się do
jednoczesnego współdzielenia zasobów przez wielu użytkowników. Można tu
wyróżnić dwa główne zagadnienia: izolowanie transakcji i systemy sterowania
współbieżnością. Poziomy izolowania transakcji określają zakres dostępu
w ramach danej transakcji dla modyfikacji bazy danych przeprowadzanych
w innych współbieżnych transakcjach. Systemy sterowania współbieżnością ściśle
wiążą się z zagadnieniem izolowania transakcji - kontrolują one nie tylko zakres
dostępu w ramach jednej transakcji - dla aktualizacji przeprowadzanych przez inne
transakcje, ale również sposób przeprowadzania tych aktualizacji.

Izolowanie transakcji

Terminem Izolowanie transakcji (ang. transaction isolation) opisujemy
mechanizm wykorzystywany przez systemy zarządzania bazami danych dla
odseparowania danej transakcji od rezultatów działania innej. Izolowanie
transakcji w Delphi odbywa się na trzech oddzielnych poziomach, z których każdy
ma odrębny charakter. Poziomy te (w skrócie poziomy TIL, od słów Transaction
Isolation
Level) określają zakres dostępu, jakim możemy dysponować w ramach
danej transakcji do modyfikacji dokonywanych w bazie danych przez inną
transakcję współbieżną.

System Delphi obsługuje za nas problemy związane z transakcjami, tzn. sam ustala
domyślny poziom TIL oraz automatycznie rozpoczyna i zatwierdza transakcje
podczas uaktualniania bazy danych przez naszą aplikację. Szerszą kontrolę
zapewni nam Passthrough SQL lub komponent

TDatabase

. Transakcjami zaleca

się sterować za pośrednictwem komponentu

TDatabase

, gdyż tylko wówczas,

przetwarzane przez nas transakcje będą widoczne dla Delphi.

Wybór odpowiedniego poziomu izolowania transakcji

Większość serwerów baz danych, łącznie z InterBase, obsługuje trzy oddzielne
poziomy izolowania transakcji. Ujmując rzecz ściśle z

punktu widzenia

zarządzania, w całej aplikacji powinno się stosować ten sam poziom TIL. Jeszcze
lepszym podejściem jest przyjęcie tego samego poziomu TIL we wszystkich
aplikacjach bazy danych. Taka strategia projektowa zapewni lepszą ochronę

background image

650

Część IV

integralności bazy danych i sprawi, że efekty przeprowadzanych modyfikacji będą
zgodne z naszymi oczekiwaniami.

Z drugiej strony jednak, wymagania konkretnej aplikacji mogą uniemożliwić nam
przyjęcie tak jednolitej strategii. Obstawanie przy niej za wszelką cenę może
prowadzić do zbyt restrykcyjnej lub niepewnej ochrony integralności bazy danych.
Mogłoby wówczas okazać się, że główne czynności naszej bazy to niepotrzebne
blokowanie (lock) tablic lub zapobieganie nieszkodliwym aktualizacjom.

WSKAZÓWKA

Implementując poziomy TIL musimy najpierw zapewnić integralność bazy danych,
a dopiero potem optymalizować wydajność na drodze udoskonalania przyjętego
schematu izolowania transakcji.

Klasyczne problemy izolowania transakcji

Problemy występujące przy wyborze schematów izolowania transakcji można
podzielić na pięć grup:

„odczyty pozorne (ang. dirty reads): mogą wystąpić, gdy w ramach danej

transakcji odczytuje się nie zatwierdzone modyfikacje wprowadzone w innej
transakcji. Ponieważ nie zatwierdzoną modyfikację można wycofać (ang. roll
back
), rezultatem jej odczytania w danej transakcji (jeszcze zanim została
wycofana), będzie właśnie odczyt pozorny.

„odczyty niepowtarzalne (ang. nonrepeatable reads): występują wtedy, gdy

danej transakcji zezwala się na modyfikację wierszy, do których odczytu ma
dostęp inna transakcja. Odczyty takie nazywamy niepowtarzalnymi, ponieważ
kolejne odczyty - z powodu modyfikacji wprowadzanych w danej transakcji ze
strony innej transakcji - będą się między sobą różnić. Ze względu na swoją
naturę, transakcja poziomu

READ COMMITTED

umożliwia odczyty

niepowtarzalne, ponieważ modyfikacje dokonane w ramach innych transakcji
mogą być odczytywane w trakcie zatwierdzania.

„wiersze-widma (ang. phantom rows): pojawiają się wtedy, gdy w transakcji

zezwala się na wybór niekompletnego zbioru nowych wierszy zapisanych
w ramach innej transakcji. Poziom izolowania transakcji, określany jako

READ

COMMITTED,

nie zapobiega występowaniu wierszy-widm.

„zgubione aktualizacje (ang. lost updates): występują wtedy, gdy w ramach

transakcji omyłkowo nadpisana zostanie (overwrite) modyfikacja dopiero co
wprowadzona w innej współbieżnej transakcji.

background image

Rozdział 23 Sterowanie współbieżnością

651

„efekty uboczne aktualizacji (ang. udpdate side effects): występują wówczas, gdy

nie wprowadzono odpowiednich ograniczeń, mających na celu ochronę
integralności i gdy wartości w wierszach są od siebie zależne. Kiedy w ramach
dwóch (lub więcej) jednoczesnych transakcji odczytywane są i uaktualniane te
same dane, niepożądane efekty uboczne mogą wystąpić wtedy, gdy pierwsza
transakcja kopiuje wartość z jednego wiersza do innego, po czym druga
zmienia tę wartość w pierwszym wierszu. Taki typ załamań integralności
określany jest terminem transakcji przeplatanych (ang. interleaved
transactions).

Sposób rozwiązywania powyższych problemów przez poziom TIL (który
wybieramy z

wykorzystaniem własności

TransIsolation

komponentu

TDatabase

), zależy od platformy RDBMS i poziomu TIL serwera (oba aspekty

omawiamy w poniższych podrozdziałach).

Zarządzanie transakcjami z wykorzystaniem TDatabase

Zarządzając transakcjami poprzez komponent

TDatabase

korzystać będziemy

z własności

TransIsolation

oraz metod:

StartTransaction

,

Commit

i

RollBack

.

TransIsolation

pozwala ustalić poziom izolowania transakcji w serwerze

bazy danych dla konkretnego połączenia. Jak już wspominaliśmy, poziom TIL
używany dla każdego połączenia w serwerze steruje dostępem innych transakcji
współbieżnych do wprowadzonych przez nas modyfikacji, a także zdolnością
naszej transakcji do obserwowania modyfikacji, wprowadzonych w innych
transakcjach współbieżnych.

TransIsolation

może przyjmować jedną z trzech wartości:

tiDirtyRead

,

tiReadCommitted

oraz

tiRepeatableRead (

domyślną jest

tiRead

Committed):

„

tiDirtyRead

- widoczne są nie zatwierdzone modyfikacje, wprowadzone

w ramach innych transakcji

„

tiReadCommitted

- widoczne są tylko modyfikacje zatwierdzone,

wprowadzone w ramach innych transakcji

„

tiRepeatableRead

- nie są widoczne modyfikacje, dokonane przez inne

transakcje w

danych poprzednio odczytanych, co oznacza, że przy

każdorazowym odczycie rekordu w transakcji, zwracany jest dokładnie ten sam
rekord.

Metoda

StartTransaction

komponentu

TDatabase

zaznacza początek

transakcji, czyli grupy modyfikacji (bazy danych), którą chcemy wydzielić jako

background image

652

Część IV

całość. Do bazy danych zostaną wprowadzone wszystkie te modyfikacje, albo
żadna z nich.

Metoda

Commit

zatwierdza modyfikacje bazy danych, które wystąpiły od chwili

rozpoczęcia transakcji. Możemy ją traktować jak komendę

save

(zachowaj)

bazy danych.

Rollback

unieważnia zmiany wprowadzone do bazy danych od momentu

uruchomienia transakcji. Możemy ją traktować jak komendę

undo

bazy danych.

Własność

TransIsolation a

p

oziomy izolowania transakcji w DBMS

Poziomy TIL, obsługiwane przez własność

TransIsolation

komponentu

TDatabase,

mogą być inne w naszym serwerze bazy danych, bądź też w ogóle

przez niego nie obsługiwane. Kiedy jeden z żądanych przez nas poziomów
własności

TransIsolation

nie jest obsługiwany przez serwer, przekazywany

jest do następnego, wyższego poziomu izolowania transakcji.

Tabela 23.1. Własność

TransIsolation

a

poziomy izolowania transakcji

w różnych platformach DBMS

Wartość własności

TransIsolation

InterBase

Oracle Sybase

i Microsoft

tiDirtyRead

odczyt
zatwierdzonych
(read committed)

odczyt
zatwierdzonych
(read committed)

odczyt
zatwierdzonych
(read committed)

tiReadCommitted

odczyt
zatwierdzonych
(read committed)

odczyt
zatwierdzonych
(read committed)

odczyt
zatwierdzonych
(read committed)

tiRepeatableRead

odczyt

powtarzalny
(repeatable read)

błąd odczytu
powtarzalnego
(TYLKO DO
ODCZYTU)
(poziom
nieobsługiwany)
(repeatable read
(READ ONLY)
Error)

Sterowanie transakcjami z wykorzystaniem języka SQL

Przetwarzaniem transakcji przez serwer można również sterować za pomocą
języka Passthrough SQL. W tym celu należy posłużyć się komendami SQL, które

background image

Rozdział 23 Sterowanie współbieżnością

653

służą do zmiany sposobu przetwarzania transakcji w serwerze. W poniższych
przykładach użyliśmy składni SQL w wersji InterBase.

UWAGA:

Pamiętajmy, że jeśli za pomocą

SQLPASSTHRUMODE

ustawiliśmy poziom

izolowania transakcji na

SHARED AUTOCOMMIT

lub

SHARED NOAUTO-

COMMIT

, to nowy poziom TIL może wpłynąć (mimowolnie) na inne transakcje

zainicjowane przez naszą aplikację.

Izolowanie transakcji

Aby ustawić, pracując za pomocą SQL, żądany poziom TIL, należy skorzystać
z komendy

SET TRANSACTION

. Np. obsługiwane w systemie InterBase trzy

poziomy TIL to

SNAPSHOT

,

SNAPSHOT TABLE STABILITY i

READ

COMMITTED

(opisane poniżej). W

celu zainstalowania jednego z

nich

powinniśmy użyć komendy SQL

SET TRANSACTION ISOLATION LEVEL

.

„

SNAPSHOT

: Baza danych przyjmuje postać obrazu statycznego (snapshot) -

w odniesieniu do momentu rozpoczęcia transakcji. Modyfikacje dokonane
przez inne aktywne transakcje nie są widoczne.

„

SNAPSHOT TABLE STABILITY

: Zablokowanie tabel, odczytywanych lub

zapisywanych w ramach danej transakcji, z umożliwieniem odczytu (tzn.
dostępu typu read-only) innym transakcjom.

„

READ COMMITTED

: w bazie danych widoczne są ostatnio zatwierdzone

wersje istniejącego wiersza (rekordu), przy czym można wprowadzać takie
modyfikacje, które nie spowodują konfliktu z

innymi, współbieżnymi

transakcjami. Dla tego poziomu można określić dwa parametry:

NO RECORD_VERSION

(parametr domyślny): Ukazuje tylko ostatnią

wersję wiersza (czyli rekordu). Jeśli dla komendy

SET TRANSACTION

podamy parametr

WAIT

, wówczas transakcja zaczeka na zatwierdzenie lub

wycofanie ostatniej wersji rekordu, po czym ponownie dokona odczytu.

RECORD_VERSION

: Odczytuje ostatnią zatwierdzoną wersję wiersza -

nawet jeśli w bazie danych znajduje się nowsza, nie zatwierdzona wersja.

Izolowanie transakcji na serwerze. Klasyczne problemy izolowania
transakcji

Metody rozwiązywania klasycznych problemów izolowania transakcji zależne są
od - obsługiwanego na serwerze - poziomu TIL (tablica 23.3).

background image

654

Część IV

Tabela 23.2. Poziomy izolowania transakcji w InterBase

Poziom TIL

Problem

Rozwiązanie

SNAPSHOT

(obraz statyczny

)

zgubione aktualizacje W innych transakcjach nie można

uaktualniać wierszy, które są
uaktualniane w ramach danej
transakcji

pozorne odczyty

Nie można odczytywać modyfikacji
dokonanych w ramach innych
transakcji; w innych transakcjach
widoczna jest poprzednia wersja
wiersza uaktualnionego w ramach
danej transakcji

niepowtarzalne

odczyty

Możliwość odczytywania tylko tej
wersji wiersza, którą zatwierdzono
przy rozpoczęciu transakcji

wiersze-widma

Możliwość odczytywania tylko tej
wersji wiersza, którą zatwierdzono
przy rozpoczęciu transakcji

efekty

uboczne

aktualizacji

Nie można odczytywać modyfikacji
dokonanych w ramach innych
transakcji; w innych transakcjach
widoczna jest poprzednia wersja
wiersza uaktualnionego w ramach
danej transakcji

READ
COMMITTED

(odczyt
zatwierdzonych)

zgubione aktualizacje W innych transakcjach nie można

uaktualniać wierszy, które są
uaktualniane w ramach danej
transakcji

pozorne odczyty

W innych transakcjach widoczna
jest poprzednia lub zatwierdzona
wersja wiersza uaktualnionego
w ramach danej transakcji

niepowtarzalne

odczyty

dozwolone z definicji

wiersze-widma

wiersze-widma

mogą się pojawić,

ponieważ na tym poziomie TIL
widoczne są zatwierdzone
modyfikacje, wprowadzone

background image

Rozdział 23 Sterowanie współbieżnością

655

Poziom TIL

Problem

Rozwiązanie
w innych transakcjach

efekty

uboczne

aktualizacji

W innych transakcjach widoczna
jest poprzednia lub zatwierdzona
wersja wiersza uaktualnionego
w ramach danej transakcji

SNAPSHOT
TABLE
STABILITY

(stabilność obrazu
statycznego tabeli)

zgubione aktualizacje Inne transakcje nie mogą

uaktualniać tabel, których dotyczy
ten poziom

pozorne odczyty

Inne transakcje nie mają dostępu do
uaktualnianych tabel, których
dotyczy ten poziom

niepowtarzalne

odczyty

Możliwość odczytywania tylko tej
wersji wiersza, którą zatwierdzono
przy rozpoczęciu transakcji; inne
transakcje nie mają dostępu do
uaktualnianych tabel, których
dotyczy ten poziom

wiersze-widma

Inne transakcje nie mają dostępu do
tabel, których dotyczy ten poziom

efekty

uboczne

aktualizacji

Inne transakcje nie mogą
uaktualniać tabel, których dotyczy
ten poziom

Wybór odpowiedniego poziomu izolowania transakcji

Jak widać, prawie każdy aspekt izolowania transakcji wiąże się (w taki czy inny
sposób) z wszystkimi (trzema) poziomami izolowania transakcji w InterBase.
Poziom, który powinniśmy wybrać, zależy w dużym stopniu od wymagań naszej
aplikacji.

SNAPSHOT

(obraz statyczny)

to domyślny poziom TIL. Dla większości aplikacji

należy zastosować właśnie poziom

SNAPSHOT

albo

READ COMMITTED

(odczyt

zatwierdzonych).

Zastosowanie poziomu

SNAPSHOT TABLE STABILITY

może - w nieokreślony bliżej sposób - zablokować dostęp innym użytkownikom do
potrzebnych im tabel. Stąd powinniśmy go raczej unikać.

Decydując się na wybór pomiędzy poziomami

SNAPSHOT

a

READ COMMITTED

powinniśmy odpowiedzieć sobie na pytanie, czy w trakcie działania naszej

background image

656

Część IV

transakcji mają być widziane zatwierdzone aktualizacje, przeprowadzane
w

ramach innych transakcji. Jeśli nie mają, powinniśmy wybrać poziom

SNAPSHOT.

W

przeciwnym razie

- READ COMMITTED

. Ogólnie rzecz

biorąc, z poziomem TIL

READ COMMITTED

wiąże się mniejsza liczba konfliktów

blokowania (lock contention).

Sterowanie transakcjami

Sterowanie transakcjami w InterBase przebiega w oparciu o następujące komendy
języka SQL:

SET TRANSACTION

,

COMMITT

i

ROLLBACK

.

SET

TRANSACTION

ma wiele zastosowań, a

jednym z

nich jest (jak już

wspominaliśmy) ustawianie poziomu izolowania transakcji. Działanie komendy

COMMIT

jest bardzo podobne do metody

Commit

własności

TDatabase -

funkcjonuje więc

jak komenda

save

(Zachowaj) bazy danych. Analogicznie,

komenda

ROLLBACK

funkcjonuje jak metoda

Rollback

tej własności -

unieważnia modyfikacje wprowadzone do bazy danych od chwili wystąpienia
ostatniej komendy

COMMIT

.

Komenda SET TRANSACTION

Komendę tą stosujemy do rozpoczęcia transakcji, np.

SET TRANSACTION

Jeżeli chcemy rozpocząć transakcję typu tylko do odczytu (read-only), musimy
dodać opcjonalne słowo kluczowe

READ ONLY

, np.

SET TRANSACTION READ ONLY

Wiele platform RDBMS obsługuje również transakcje nazwane (named
transactions
). Umożliwia to m.in. zagnieżdżanie transakcji. W języku InterBase
SQL komendy do modyfikacji danych (takie jak

INSERT

,

UPDATE

i

DELETE

)

mogą się bezpośrednio odwoływać do transakcji nazwanych. Oto składnia
przykładowego wyrażenia w InterBase, poprzez które uruchamiamy transakcję
nazwaną:

SET TRANSACTION :UpdateCustomers

Zauważmy, że zmienna

:UpdateCustomers

musi być wcześniej

zadeklarowana i zainicjowana.

A oto składnia wyrażenia w języku Sybase Transact-SQL, wpisanego w tym
samym celu:

BEGIN TRANSACTION UpdateCustomers

background image

Rozdział 23 Sterowanie współbieżnością

657

Komendy COMMIT i ROLLBACK

Komenda

COMMIT

języka SQL powoduje utrwalenie modyfikacji, dokonanych

w trakcie danej transakcji. Zachowuje ona modyfikacje, które wprowadziliśmy do
bazy danych i zapewnia, że będą one widoczne dla innych użytkowników.
Z drugiej strony komenda

ROLLBACK

unieważnia wszystkie modyfikacje, które

dana transakcja mogła wprowadzić do bazy danych. W większości systemów
usuwa ona po prostu wszystkie modyfikacje - oczekujące na zatwierdzenie
i

wprowadzone do protokołu transakcji lub wycofań. Obydwie z

wyżej

wymienionych komend wpływają na modyfikacje, które wystąpiły od ostatniej
komendy

COMMIT.

Nie można natomiast wycofać (rollback) modyfikacji już

zatwierdzonych.

UWAGA:

Między komendami

SET TRANSACTION a COMMIT

lub

ROLLBACK

powinno

umieszczać się tylko komendy bezpośrednio związane z modyfikacją danych.
Przeglądanie i inne czynności związane z odczytywaniem informacji powinny być
przeprowadzane przed zastosowaniem komendy

SET TRANSACTION

. Takie

podejście zapewnia możliwie najkrótszy czas blokady, w którym inni użytkownicy
nie będą mieli dostępu do zasobów bazy danych.

Edytor WISQL, udostępniany przez InterBase, automatycznie rozpoczyna
transakcję (poprzez wydanie komendy, będącej odpowiednikiem InterBase

SET

TRANSACTION

) przy pierwszym uruchomieniu. Tuż przed zakończeniem pracy

z WISQL zostaniemy zapytani o to, czy chcemy zatwierdzić efekty naszej pracy.
Możemy je w każdej chwili zatwierdzić lub wycofać - opcją

Commit Work

lub

Rollback Work

- z menu

File

edytora WISQL.

Systemy sterowania współbieżnością

System sterowania współbieżnością możemy np. odnieść do zarządzania
magistralą kolejową. Gdyby jeździł po niej tylko jeden pociąg, to w ogóle nie
byłoby problemu ze współbieżnością; jeżeli jednak pojawi ich więcej, trzeba
szybko opracować jakiś plan, który zapobiegnie kolizjom a jednocześnie zapewni,
że towary będą odbierane i ładowane na czas. Takie same problemy napotykamy
przy bazach danych. Musimy znaleźć sposób zapobiegania ,,kolizjom’’ (tzn.
wzajemnemu nadpisywaniu) aktualizacji wprowadzanych przez poszczególnych
użytkowników, przy zachowaniu oczekiwanej przez nich wydajności.

Oczywiście nie będzie problemów ze współbieżnością, jeśli do bazy danych
kierować będziemy jedynie zapytania. Pojawiają się one dopiero wówczas, gdy
dwóch lub więcej użytkowników zechce zmodyfikować te same dane w tym

background image

658

Część IV

samym czasie. Kiedy modyfikacje jednego użytkownika zostają zgubione lub nie
może on ich wprowadzić z powodu modyfikacji przeprowadzanych przez innego
użytkownika - wtedy mówimy, że wystąpiła zgubiona aktualizacja (lost update)
lub konflikt aktualizacji (update conflict). Efektywne systemy sterowania
współbieżnością rozwiązują te problemy z

zachowaniem użyteczności

i funkcjonalności bazy danych.

Wyróżniamy dwa podstawowe typy systemów sterowania współbieżnością:
pesymistyczny i

optymistyczny. Często określa się je jako systemy

optymistycznego i pesymistycznego blokowania (locking) zasobów bazy danych,
gdyż właśnie blokowanie wykorzystywane jest najczęściej przy rozwiązywaniu
problemów współbieżności. Systemy te rozróżnia się według leżących u ich
podstaw założeń, odnoszących się do prawdopodobieństwa konfliktu, czyli próby
modyfikacji tych samych danych w kilku transakcjach współbieżnych w tym
samym czasie. W pesymistycznym systemie sterowania współbieżnością zakłada
się prawdopodobieństwo takiego konfliktu, podczas gdy w

systemie

optymistycznym uważa się go za zdarzenie nienormalne i traktuje jako wyjątek.
W

optymistycznym systemie sterowania współbieżnością przyjmuje się, iż

większość zapytań jest typu tylko do odczytu (read-only) i że aktualizacje
konkretnej danej w tym samym czasie dokonywane są rzadko.

Pesymistyczne sterowanie współbieżnością

Konsekwencją przyjętego - w pesymistycznym systemie sterowania współbież-
nością - dużego prawdopodobieństwa występowania konfliktów aktualizacji jest
blokowanie dostępu do zasobów wykorzystywanych w danej transakcji. Inne
transakcje, które potrzebują tych zasobów, muszą zaczekać, aż transakcja
blokująca zakończy przetwarzanie.

Chociaż pesymistyczny system sterowania może korzystać zarówno z blokad
odczytu (read-inhibit lock), jak i blokad zapisu (write-inhibit lock), w praktyce
stosowane są jedynie te ostatnie. Zwykle mamy do czynienia z blokadami, które
zezwalają tylko na odczyt danych, lub takimi, które zezwalają zarówno na ich
odczyt, jak i zapis. Dostęp do danych można blokować na różnych poziomach - od
wielu tabel na raz do pojedynczych kolumn.

Najczęściej stosuje się (platforma SQL Server) blokowanie tabel, wierszy i stron.
Blokady takie mogą czasem wystąpić jedna po drugiej. Oznacza to, że po
blokadzie kilku wierszy może wystąpić blokada całej, zawierającej je tabeli.

Nazywamy to eskalacją blokad (lock escalation). Występuje ona wówczas, gdy
serwer bazy danych oceni, że liczba już istniejących blokad usprawiedliwia
zastąpienie ich blokadą całej tabeli.

background image

Rozdział 23 Sterowanie współbieżnością

659

W praktyce nigdy nie spotykamy się z blokadami indywidualnych kolumn;
obciążają one zasoby i

nie służą żadnemu konkretnemu celowi. Rzadko

uzasadnienie znajduje zapobieganie modyfikacji kolumny przecinającej się
z wierszem, który nie jest uaktualniany w innej transakcji.

Ogólnie - z im większą precyzją blokujemy dostęp do bazy danych, tym więcej
użytkowników może go do niej jednocześnie uzyskać. Z kolei - im więcej
korzystających jednocześnie z bazy użytkowników, tym większa staje się liczba
blokad. Jeżeli zablokowaliśmy tylko jedną tabelę, mamy tylko jedną blokadę.
Jeżeli jednak zablokujemy wiele wierszy w wielu tabelach, to wystąpi wiele
blokad. Im więcej blokad, tym więcej zasobów serwera musi zostać
wykorzystanych do zarządzania blokadami i tym dłuższy staje się czas konieczny
do ich zakładania i usuwania. Podobnie jak w wielu innych przypadkach, tak i tutaj
- większa funkcjonalność oznacza również zwiększone obciążenie zasobów.

Z powodu naturalnej tendencji do blokowania całych tabel, pesymistyczne systemy
sterowania podatne są na zatory (deadlock; przyp.tłum**. prawidłowym polskim
tłumaczeniem słowa deadlock w programowaniu współbieżnym jest termin
blokada, tutaj jednak ma on inne znaczenie, i dlatego zdecydowano się tłumaczyć
je jako zator). Zator występuje wówczas, gdy transakcja nr 1 blokuje tabele
wymagane w transakcji nr 2 i odwrotnie. Ponieważ w żadnej z tych transakcji nie
można zablokować tabel, które są potrzebne do jej zakończenia, obie pozostają
zamknięte niczym w śmiertelnym uścisku, i żadna z nich nigdy się nie skończy bez
interwencji z zewnątrz.

Z reguły w serwerach baz danych realizuje się pesymistyczną strategię sterowania.
Jest to zaszłość z czasów, gdy żadne zasoby nie istniały na poziomie stacji
roboczej, z

którego to powodu nie można było stosować mechanizmów

optymistycznych. Ponieważ w

samym Delphi przyjęto właśnie strategię

optymistyczną, w pracy z naszymi aplikacjami możemy zetknąć się z dwiema
strategiami.

Większość serwerów baz danych automatycznie blokuje za nas odpowiednie
elementy danych w trakcie wprowadzania modyfikacji do bazy. Kiedy np.
modyfikujemy wiersz, większość serwerów blokuje go, aby uniemożliwić innym
użytkownikom jego aktualizację w tym samym czasie. Serwery pracujące na
niektórych platformach mogą zablokować całą tabelę, jeżeli usuwamy dużą liczbę
wierszy.

Zarówno w Sybase, jak i Microsoft SQL Server przyjęto blokowanie całych tabel
i stron, a nie pojedynczych wierszy, co z kolei ma miejsce w platformach takich
jak Oracle. Kiedy próbujemy zmodyfikować dany wiersz, strona, na której się on
znajduje, zostaje zablokowana na czas uaktualniania. Aktualizacje zwykle
występują w grupach dotyczących wierszy zgromadzonych w danym obszarze
bazy danych. Jeśli przyjąć blokowanie tych wierszy według stron, zwykle jedna

background image

660

Część IV

blokada wystarcza dla kilku wierszy. Zmniejsza to obciążenie zasobów serwera,
ale może uniemożliwić dostęp do wierszy, których dana aktualizacja nie dotyczy.
W takiej strategii szczególny problem sprawiają aktualizacje pojedynczych
wierszy. Pomijając kwestie opłacalności, podejście przyjęte przez firmę Microsoft
w platformie SQL Server okazuje się w praktyce równie dobre, co przyjęte
w platformach innych producentów DBMS.

UWAGA:

Ostatnio w platformie Microsoft SQL Server pojawiła się możliwość blokowania
wierszy w pewnych, dość rzadkich, sytuacjach, ale udogodnienie to działa
znacznie gorzej, niż obsługa blokowania wierszy zrealizowana w systemie Oracle.
Również w Sybase testuje się obecnie mechanizm blokowania wierszy, który
jednak nie jest jeszcze dostępny na rynku.

Optymistyczne sterowanie współbieżnością

W systemach optymistycznego sterowania współbieżnością zakłada się, że
aktualizacje przeważnie nie wpadają we wzajemne konflikty i że większość
użytkowników bazy danych odczytuje informacje, ale ich nie uaktualnia. Poniższy
przykład powinien pomóc w zrozumieniu takiego systemu. Wyobraźmy sobie
głównego redaktora nadzorującego redagowanie rękopisów książek, które mają
zostać opublikowane. Kiedy otrzymuje on rękopis, powiela go i

rozsyła

podwładnym, którzy z kolei dodają do swoich kopii poprawki i zwracają je.
Następnie redaktor główny musi zintegrować wszystkie modyfikacje i wprowadzić
je do oryginału. Ci redaktorzy, którzy otrzymają poprawiony rękopis, nie muszą
dzięki temu powtarzać wykonanej już pracy.

Powyższy przykład z kilku powodów dobrze ilustruje działanie optymistycznego
systemu sterowania współbieżnością. Po pierwsze zauważmy, że oryginał rękopisu
nigdy nie opuszcza głównego redaktora. Podobnie w systemach optymistycznego
sterowania najpierw modyfikuje się kopię danych. Sam serwer wprowadza
modyfikację w taki sposób, żeby nie nadpisała (overwrite) ona modyfikacji innych
użytkowników.

Zauważmy również naturalny podział modyfikacji, wprowadzanych przez
poszczególnych redaktorów. Jeden z nich sprawdza np. rękopis pod względem
ortografii, inny może być odpowiedzialny za poprawność merytoryczną i tak dalej.
Z reguły dwóch redaktorów nigdy jednocześnie nie redaguje tego samego rękopisu
pod tym samym względem. Oto podstawowe założenie każdego optymistycznego
systemu sterowania współbieżnością. Aktualizacje bazy danych przedsiębiorstwa
dzieli się zwykle według oddziału, poziomu zarządzania lub innych kryteriów.

background image

Rozdział 23 Sterowanie współbieżnością

661

Sprawia to, że system optymistyczny działa dobrze, ponieważ zasoby bazy danych
nie są początkowo blokowane.

Optymistyczne systemy zarządzania współbieżnością w Delphi

Optymistyczne sterowanie współbieżnością udostępniane jest w Delphi przez
własność

UpdateMode

komponentów

TTable

oraz

TQuery DataSet

.

Podobnie jak w innych optymistycznych systemach współbieżnych, Delphi
otrzymuje z serwera kopię wiersza, udostępnia ją nam do modyfikacji, po czym
przesyła mu ją z powrotem. Przekaz ten realizujemy poprzez komendę

UPDATE

w SQL. Komenda w przedstawionej tutaj postaci zawiera klauzulę

WHERE

, służącą

wyszukaniu wiersza do modyfikacji. Poprzez

UpdateMode

definiowane są te

kolumny tabeli, które mają zostać uwzględnione przez klauzulę

WHERE

.

Własność

UpdateMode

może przyjmować jedną z trzech wartości:

„

upWhereAll

(wartość domyślna) - Klauzula

WHERE

uwzględni każdą

kolumnę w tabeli.

„

upWhereChanged

- Klauzula

WHERE

uwzględni kolumny według klucza

z

DataSet

oraz kolumny, które uległy zmianie.

„

upWhereKeyOnly

-

Klauzula

WHERE

uwzględni tylko kolumny według

klucza z

DataSet

(tego ustawienia należy używać tylko wówczas, gdy mamy

wyłączność do tabeli bazy danych).

Obydwa komponenty -

TTable

i

TQuery -

propagują własność

UpdateMode

.

Jej ustawieniem domyślnym jest

upWhereAll

, co oznacza, że uaktualnienie

wiersza w tabeli spowoduje wygenerowanie klauzuli

WHERE

, wyświetlającej

wszystkie jej kolumny. Może to okazać się bardzo kłopotliwe, zwłaszcza
w przypadku dużych tabel. Alternatywne i szybsze rozwiązanie polega na nadaniu
własności

UpdateMode

wartości

upWhereChanged

. W

wyniku takiego

ustawienia generowana będzie klauzula

WHERE

, która uwzględni jedynie kluczowe

i zmodyfikowane pola tabeli.

Przypuśćmy, że nasza aplikacja w Delphi zmodyfikowała już pole

LastName

tabeli CUSTOMER. Poniżej zamieściliśmy kod SQL, wygenerowany przy
ustawieniu

upWhereAll

(zwróćmy uwagę na długość klauzuli

WHERE)

:

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

background image

662

Część IV

A oto kod wygenerowany przy ustawieniu

upWhereChanged

:

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

Zauważmy, o ile krótszy jest drugi listing. Zwróćmy również uwagę na to, że
dzięki umieszczeniu w klauzuli

WHERE

oryginalnej wartości pola

LastName

,

w drugim przypadku uniknięto możliwości nadpisania (overwrite) ewentualnej
modyfikacji tego pola, wprowadzonej przez innego użytkownika.

Jeżeli inny użytkownik zmodyfikuje pole

LastName

w czasie, jaki upłynął

między odczytem wiersza a uaktualnieniem go przez bieżącego użytkownika,
komenda

UPDATE,

wygenerowana w efekcie ustawienia

upWhereChanged,

zakończy się niepowodzeniem, i o to nam właśnie chodzi. Oczywiście jeszcze
większą odporność na nieoczekiwane konflikty daje ustawienie

upWhereAll

.

Inny użytkownik mógłby usunąć wiersz po przeczytaniu go przez naszą aplikację,
a potem dodać nowy rekord do tabeli, który przez przypadek może mieć ten sam
klucz i tę samą wartość

LastName

, co poprzedni rekord. Jeśli nasz rekord użyłby

swojej komendy

UPDATE

, to uaktualniłaby ona niewłaściwy rekord. Taki

scenariusz jest jednak bardzo mało prawdopodobny.

Inna wartość

UpdateMode

, a mianowicie

upWhereKeyOnly

, jeszcze bardziej

redukuje odporność na konflikty. Ponieważ wówczas sprawdza się wartości klucza
tylko dla tego pola, które modyfikujemy, nie uwzględnia się jego ewentualnej
modyfikacji przez innego użytkownika - w czasie, jaki upłynął od pierwszego
odczytu rekordu. Pominięcie takiej możliwości może być bezpiecznym
założeniem, ale (tak jak poprzednio), niestety nie zawsze. W

większości

zastosowań z wieloma użytkownikami niebezpiecznie jest zakładać, że inny
użytkownik nie zmodyfikował jeszcze rekordu, który odczytaliśmy w aplikacji
klienckiej. Tak więc optymalizację taką przeprowadzamy w rzadkich przypadkach
i

tylko wówczas, kiedy jest ona niezbędna. Ponieważ klauzula

WHERE,

wygenerowana przez ustawienie

upWhereKeyOnly,

jest z natury rzeczy

krótsza, działa ona szybciej od klauzuli obejmującej dodatkowe kolumny.
Niemniej jednak, przed skorzystaniem z tej opcji powinniśmy skonsultować się
z administratorem bazy danych - by nie narazić się na utratę danych.

W praktyce powinniśmy się trzymać ustawienia

upWhereAll (

chyba że mamy

konkretny powód, żeby wykorzystać inne). Dobrą alternatywą jest wartość

upWhereChanged -

jeżeli tabela, z którą pracujemy, ma dużo kolumn,

a ustawienie

upWhereAll

istotnie spowalnia pracę.

background image

Rozdział 23 Sterowanie współbieżnością

663

Zarządzanie protokołem transakcji

Zastanówmy się, jak serwery baz danych unikają ciągłego zachowywania
modyfikacji, na wypadek zaniku zasilania lub uszkodzenia sprzętu? Czy serwer
dysponuje czymś w rodzaju listy wycofań? Skąd serwer wie, które transakcje mają
zostać unieważnione podczas odtwarzania bazy danych po awarii?

Odpowiedź na wszystkie te pytania jest następująca. Otóż modyfikacje
wprowadzane przez nas do bazy danych są najpierw wprowadzane do protokołu
transakcji (lub protokołu wycofań) bazy danych, a dopiero potem zatwierdzane
w samej bazie, po pomyślnym zakończeniu transakcji. Oznacza to, że protokół
transakcji jest czymś więcej niż tylko protokołem, to jego dotyczą wszystkie
operacje! Tak więc, jeżeli zanik zasilania serwera wystąpił, zanim w bazie danych
zatwierdzono naszą modyfikację, to żadnej zmiany nie trzeba wycofywać, gdyż
w ogóle nie zaistniała ona w tej bazie. Ponieważ nasze nie zatwierdzone
modyfikacje były wprowadzane tylko do protokołu transakcji, oprogramowanie
serwera może w łatwy sposób unieważnić nie zakończoną transakcję, bez
odwoływania się do samej bazy danych.

Jednym z zagadnień, o którym często zapominają twórcy aplikacji baz danych, jest
zarządzanie protokołem transakcji. Zaniedbuje się je zwykle i odkłada na bok, jako
należące do dziedziny administrowania bazami danych.

Istnieje jednak kilka zagadnień związanych z zarządzaniem protokołami transakcji,
z którymi kompetentny twórca oprogramowania dla baz danych typu klient/serwer
musi się zapoznać i które powinien brać pod uwagę w swoich aplikacjach.
Pierwsze z

nich wiąże się z

koniecznością minimalizowania informacji

przechowywanych w protokole transakcji. Drugie - z zaleceniem rozbijania dużych
transakcji na mniejsze, w celu uniknięcia zapełnienia protokołu. Oba zagadnienia
omówiono po kolei w poniższych podrozdziałach.

Minimalizowanie informacji pamiętanych w protokole

Częstym błędem popełnianym przez twórców oprogramowania typu klient/serwer,
którzy wywodzą się ze świata prostych baz danych (tzn. opartych na prostych
plikach, flat files) - takich jak dBASE czy Paradox - jest przeprowadzanie
aktualizacji grupowych z użyciem konstrukcji programowych, a nie języka SQL.
Dobrze ilustruje to poniższy przykład. Przypuśćmy, że musimy zamienić -
z małych na wielkie - litery wszystkich pól nazwisk w tabeli CUSTOMER. Ci
z nas, którzy zdobywali pierwsze doświadczenia pracując z językiem dBASE,
mogliby prawdopodobnie napisać taki oto fragment w języku Object Pascal:

With taCUSTOMER do begin
First;

While not EOF do begin

background image

664

Część IV

FieldByName(‘LastName’).AsString

:=

UpperCase(FieldByName(‘LastName’).AsString);
Next;
end;
end;

Rezultatem takiej strategii jest nie tylko mała szybkość działania, ale też
niepożądane, oddzielne rozpoczynanie i zatwierdzanie transakcji dla każdego
kolejnego wiersza w tabeli. Ponadto, jeśli podczas wykonywania pętli wystąpiłby
jakiś problem, to tylko część wierszy zostałaby uaktualniona - litery w niektórych
nazwiskach zostałyby zamienione na wielkie, a w innych nie uległy zmianie.
Problemowi częściowej aktualizacji można by zaradzić przez wywołanie metody

StartTransaction

naszego komponentu

TDataBase

jeszcze przed

wejściem do pętli. Jednak proces aktualizacji w dalszym ciągu przebiegałby
z niską wydajnością. W każdym, pojedynczym obiegu pętli zostaje wygenerowana
oddzielna instrukcja

UPDATE

języka SQL, zakończona swoją własną klauzulą

WHERE

, której celem jest odnalezienie kolejnego wiersza. Przy bardzo dużej tabeli

CUSTOMER proces ten może ciągnąć się niezwykle długo.

Daleko lepszym podejściem, i to takim, w którym protokół transakcji wykorzystuje
się we właściwy sposób, jest wprowadzenie naszej modyfikacji z wykorzystaniem
komponentu

TQuery

lub

TStoreProc

. Odpowiednik powyższego kodu,

zapisany w języku SQL, wygląda następująco:

UPDATE CUSTOMER
SET LastName=UPPER(LastName)

Ponieważ samo powyższe wyrażenie zostanie potraktowane jako pojedyncza
transakcja, do tabeli zostaną wprowadzone albo wszystkie aktualizacje, albo
żadna. Ponadto nie będą już miały miejsca ciągłe inicjacje i zatwierdzenia
protokołu, w schemacie transakcja po transakcji.

Innym sposobem ograniczenia informacji w protokole, które generuje transakcja,
jest unikanie niekwalifikowanych komend

DELETE

języka SQL. Każdy wiersz do

usunięcia (poprzez

DELETE)

jest najpierw kopiowany do protokołu transakcji tak,

żeby można ją było, w razie potrzeby, unieważnić. W przypadku dużych tabel
można łatwo przekroczyć dopuszczalną wielkość protokołu. Jeżeli chcemy usunąć
wszystkie wiersze tabeli, powinniśmy użyć komendy serwera do opróżniania tabel
(table-truncation command), a nie

DELETE

. Wiele serwerów obsługuje komendę

podobną do komendy

ZAP

języka dBASE, która szybko opróżnia tabelę z jej

zawartości. Przykładowo - w produktach Sybase i Microsoft SQL Server istnieje,
służąca do tego celu, komenda

TRUNCATE TABLE

. Nie tylko działa ona szybciej

od

DELETE

, ale nie jest zachowywana do protokołu transakcji, w związku z czym

nie wiążą się z nią żadne operacje na protokole. Jeżeli nasz serwer nie udostępnia
komendy do opróżniania tabel, mogłoby okazać się, że lepiej jest po prostu

background image

Rozdział 23 Sterowanie współbieżnością

665

,,fizycznie’’ usunąć całą tabelę i ponownie ją utworzyć - w zależności od metody
jej stosowania gdzie indziej w bazie danych i w innych aplikacjach.

Platformy Sybase i Microsoft oferują inny sposób ograniczenia operacji na
protokole, a mianowicie zastąpienie

INSERT SELECT

(przy kopiowaniu wierszy

z tablicy do tablicy) komendą

SELECT INTO

. Wiersze wstawiane za pomocą

INSERT SELECT

przechowywane są w protokole transakcji, stąd też nie

powinniśmy wprowadzać dużych porcji danych. Komendę

SELECT INTO

zrealizowano na platformie SQL Server jako operację nie wiążącą się
z protokołem, gdyż nie powoduje ona dodawania do niego żadnych pozycji.
Składnia powyższa nie będzie obsługiwana na wszystkich platformach DBMS (nie
obsługuje jej np. InterBase). Stąd opisywana technika może okazać się
niewykonalna w konkretnym systemie.

Komenda

SELECT INTO

według składni dialektu Transact-SQL platformy SQL

Server ma następującą postać:

SELECT LastName, FirstName
INTO NEWCUSTOMER
FROM CUSTOMER

Tworzy się tutaj tabelę o nazwie NEWCUSTOMER i wstawia do niej kolumny

LastName

i

FirstName

z tabeli CUSTOMER. Kod powyższy odpowiada

funkcjonalnie następującej składni:

CREATE TABLE NEWCUSTOMER
(LastName CHAR(30),
FirstName CHAR(30))

INSERT INTO NEWCUSTOMER (LastName, FirstName)
SELECT LastName, FirstName
FROM CUSTOMER

Komendę

SELECT INTO

można wykorzystać dla uniknięcia tworzenia pozycji

w protokole transakcji, zgodnie z niżej przedstawionym scenariuszem:

„chcemy utworzyć tabelę, a następnie skopiować do niej wiersze z innej tabeli

„Wszystkie kolumny w nowej tabeli mają otrzymać wartości z tabeli źródłowej

„Nie zależy nam na możliwości wycofania wstawionych wierszy

Podział dużych transakcji na mniejsze

W niektórych sytuacjach musimy przeprowadzić modyfikacje wielkich ilości
danych, prowadzące w

nieunikniony sposób do utworzenia wielu pozycji

w protokole transakcji lub wycofań. Takie aktualizacje mogą spowodować
zapełnienie protokołu i problemy w serwerze. Mogą one również prowadzić do

background image

666

Część IV

blokad, uniemożliwiających innym użytkownikom dostęp do całych tabel. Z tego
powodu dobrze jest rozbić dużą transakcję na mniejsze. Można to przeprowadzić
na kilka sposobów - ten, który wybierzemy, zależy od posiadanej platformy DBMS
i konkretnych potrzeb naszej aplikacji.

Obszerną aktualizację można rozbić na kilka mniejszych, dzieląc poziomo
komendy służące do modyfikacji danych - tzn. rozbijając jedną komendę

UPDATE

lub

DELETE

na kilka komend dotyczących tego podzbioru wierszy, do którego

odnosiła się oryginalna klauzula

WHERE

. Gdyby tabela CUSTOMER

w poprzednim przykładzie miała milion rekordów i chcielibyśmy zamienić litery
w polu nazwiska każdego z nich, moglibyśmy napisać następujący kod:

SET TRANSACTION;
UPDATE CUSTOMER
SET LastName=UPPER(LastName)
WHERE CustomerNumber between 1 and 100000;
COMMIT;

SET TRANSACTION;
UPDATE CUSTOMER
SET LastName=UPPER(LastName)
WHERE CustomerNumber between 100001 and 200000;
COMMIT;

SET TRANSACTION;
UPDATE CUSTOMER
SET LastName=UPPER(LastName)
WHERE CustomerNumber between 200001 and 300000;
COMMIT;

Jest to prosty sposób praktycznej realizacji tak obszernej transakcji.

Inną metodą ograniczenia liczby wierszy, których dotyczy wyrażenie z komendą

UPDATE

lub

DELETE

, jest redukcja tych wierszy, które komenda ta może zmienić

w jednym przebiegu. Nie jest to możliwe we wszystkich platformach DBMS (np.
umożliwia to SQL Server, a już nie InterBase).

W tym celu musimy użyć komendy właściwej dla posiadanego serwera, która
służy do ograniczania liczby wierszy przetwarzanych przez

UPDATE

lub

DELETE

.

Komendę tę możemy powtarzać tyle razy, ile potrzeba dla przetworzenia całej
tabeli. Poniżej zamieściliśmy przykład w języku Transact-SQL systemu SQL
Server.

SET ROWCOUNT 50000 /* Limits the UPDATE to 50000 rows at a

time*/

WHILE (EXISTS (SELECT * FROM ORDERS WHERE Amount<>0))
BEGIN

UPDATE ORDERS SET Amount=0

WHERE Amount<>0/* Keeps the UPDATE from looping infinitely */

background image

Rozdział 23 Sterowanie współbieżnością

667

END

Z uwagi na fakt, iż modyfikowane wiersze ciągle znajdują się w tabeli z każdym
obiegiem pętli, musimy znaleźć sposób ich usunięcia po przetworzeniu. Jednym
z nich może być wykorzystanie klauzuli

WHERE

komendy

UPDATE

- tak, żeby

modyfikowane wiersze były przez tę klauzulę ignorowane po ich zmodyfikowaniu.
Innymi słowy musimy sprawdzać kolumny Amount z użyciem testu <> 0,
ponieważ w komendzie

UPDATE

polu Amount nadajemy wartość 0. Dzięki temu

komenda ta, modyfikując dany wiersz, zaznacza go jako już przetworzony.
Zaznaczanie takie zapobiega dwukrotnemu przetworzeniu wiersza.

A oto przykład z komendą

DELETE

:

SET ROWCOUNT 50000 /* Limits the DELETE to 50000 rows at a

time*/

WHILE (EXISTS (SELECT * FROM ORDERS WHERE Amount=0))
BEGIN

DELETE FROM ORDERS

WHERE Amount=0
END


Wyszukiwarka

Podobne podstrony:
24 rozdzial 23 wjv3mksbkbdm37qy Nieznany
24 rozdzial 23 wjv3mksbkbdm37qy Nieznany
23 24 ROZ w sprawie rodzaju Nieznany (2)
Rozdział 23-24, Dni Mroku 1 - Nocny wędrowiec
FIY Rozdział 23, 24
Dom Nocy 09 Przeznaczona rozdział 23 24 25 TŁUMACZENIE OFICJALNE
05 rozdzial 04 nzig3du5fdy5tkt5 Nieznany (2)
28 rozdzial 27 vmxgkzibmm3xcof4 Nieznany (2)
22 Rozdzial 21 KP4Q5YBIEV5DBSVC Nieznany (2)
09 08 Rozdzielnice budowlane RB Nieznany (2)
17 rozdzial 16 fq3zy7m2bu2oan6t Nieznany (2)
Kanicki Systemy Rozdzial 10 id Nieznany
24 11 id 30514 Nieznany (2)
29 rozdzial 28 ciw47mwstcqakqpq Nieznany
24 Ustalanie i podzial wyniku f Nieznany (2)
29 rozdzial 28 w3cbrpnwzh762znr Nieznany (2)
13 Rozdziae 12id 14782 Nieznany (2)
DGP 2014 06 23 rachunkowosc i a Nieznany

więcej podobnych podstron