Výkonnost databázových aplikací III

1. 4. 1998

Sdílet

Rád bych navázal na poznámky z minulého čísla a věnoval se dalším fyzickým charakteristikám nebo, chcete-li, vl...


Rád bych navázal na poznámky z minulého čísla a věnoval se
dalším fyzickým charakteristikám nebo, chcete-li, vlastnostem
SŘBD, které přímo souvisejí s výkonností aplikací pracujících
nad daným SŘBD, resp. databází.

Řízení sdíleného přístupu

Mechanismy řízení sdíleného přístupu slouží pro realizaci
víceuživatelského prostředí databázových aplikací. Základním
problém je řešení paralelního přístupu více uživatelů k témže
řádkám určité databázové tabulky. Řízení sdíleného přístupu má
dva hlavní cíle:

- zabezpečení realizace transakcí, tj. mechanismu, kdy buď
všechny změny jedné transakce budou promítnuty do databáze, nebo
nebudou promítnuty do žádné

- zabezpečení vzájemné neovlivnitelnosti transakcí

Již z definice těchto cílů vyplývá, že základní pracovní
jednotkou zmíněných mechanismů jsou transakce. Provádění
transakcí má dva základní dopady:

- dopad na data prováděním příkazů INSERT, UPDATE, DELETE

- dopad na jiné transakce, pakliže tyto načetly data zapsané
danou transakcí

První z dopadů - přímý dopad na data lze eliminovat provedením
operace ROLLBACK s využitím záznamů v log souborech (viz minulá
část seriálu). Eliminace druhého dopadu na jiné transakce je
realizována databázovými zámky. Zámky jsou mechanismy
zabezpečující synchronizaci provádění transakcí v tom smyslu, že
lze jednu transakci ukončit příkazem ROLLBACK bez vlivu na
integritu jiných transakcí, které by jinak pracovaly s daty
vytvořenými nezakomitovanou transakcí.

Pakliže není zabezpečeno dostatečné řízení sdíleného přístupu
(zejména synchronizace transakcí), může to vést k řadě
problémům. Mezi nejčastější patří:

ignorace operace ROLLBACK - abych blíže popsal tento problém,
uvedu časově uspořádané zpracování dvou transakcí:



ztracené aktualizace: k tomuto problému dojde v okamžiku, kdy
dvě či více transakcí přistoupí ke stejným řádkám databáze a
provedou úspěšnou aktualizaci těchto řádek. V případě, že nejsou
k dispozici mechanismy zámků, mohou transakce přepsat poslední
modifikace, aniž se vůbec dozvědí, že k nějakým došlo.

problém nezakomitované závislosti: pokud jedna transakce provede
ROLLBACK, může to (opět v případě, že nejsou k dispozici
mechanismy zámků) způsobit ztrátu změn provedených jinou
transakcí. Problém lze vysvětlit na následujícím příkladu: řádka
je aktualizovaná jednou transakcí, ale tato aktualizace není
zakomitovaná. Poté je aktualizována i druhou transakcí, opět bez
provedení operace COMMIT. Pakliže následně první transakce
provede operaci ROLLBACK, dojde tak ke zrušení i aktualizačních
změn provedených druhou transakcí.

Zámky

SŘBD používají dva vzájemně se vylučující typy zámků: sdílené
zámky (shared locks, S-zámky) a exkluzívní zámky ( exclusive
locks, X-zámky). Dále je obvykle využíván jeden typ dočasných
zámků, tzv. update zámky (U-zámky). Jednotlivé SŘBD se liší
podle toho, na jaké úrovni zámky nastavují - zda na úrovni
databázových stránek či na úrovni jednotlivých řádek. Diskuse o
tom, který z těchto přístupů je lepší, není dosud uzavřena a dle
mého názoru nemá jednoznačný závěr.

Všechny zámky jsou nastavovány implicitně (tj. bez speciálního
požadavku transakce) SŘBD jako reakce na různé DML příkazy
transakce. Jedinou výjimkou je klauzule FOR UPDATE OF příkazu
SELECT, která způsobí explicitní nastavení U-zámku a předpokládá
následnou aktualizaci uzamčených řádek. Možnou koexistenci
jednotlivých typů zámků nad jedinou stránkou, resp. řádkou,
ukazuje následující tabulka:



Pro realizaci uzamykacích operací používají SŘBD protokoly
nazývané izolační úrovně . Tyto protokoly definují, jak dlouho
budou zámky nad danou stránkou, resp. řádkou, nastaveny a jakým
způsobem budou zpřístupňována data pomocí message bufferů.
Izolační úroveň volí transakce v okamžiku otevření prvního
kurzoru a zůstává v platnosti po celou dobu trvání transakce.
Izolační úroveň platí pro všechny kurzory používané transakcí.
Změna izolační úrovně uprostřed transakce způsobí vygenerování
implicitního COMMITu a spuštění nové transakce s novou úrovní
izolace.

Standard SQL-92 definuje následující izolační úrovně:

- serializable (S) - je to defaultní úroveň

- repeatable read (RR) - úroveň povoluje číst pouze komitované
záznamy a dále požaduje, že mezi dvěma čteními záznamu transakcí
nemá žádná další povoleno aktualizovat tato data. Nicméně
transakce nemusí být serializovaná vzhledem k ostatním
transakcím. Např. při hledání záznamů splňujících určité
podmínky může transakce nalézt některé záznamy vložené
komitovanou transakcí, ale nemusí nalézt jiné (dosud
nekomitované)

- read committed (RC) - Úroveň umožňuje číst jen komitované
záznamy, ale nevyžaduje repeatable reads. Např. mezi dvěma
operacemi čtení záznamu jednou transakcí může záznam
aktualizovat jiná komitovaná transakce

- read uncommitted (RU) - umožňuje číst i nezakomitované
záznamy. Je to nejnižší úroveň konzistence povolená standardem
SQL-92

Při návrhu víceuživatelských databázových aplikací je třeba
zvažovat dva protichůdné faktory ovlivňující výběr izolační
úrovně:

- konzistence dat

- sdílený přístup více uživatelů (potažmo i doba odezvy)

Vyšší stupeň konzistence dat je zpravidla zabezpečen na úkor
nižšího stupně sdíleného přístupu a tedy i delší doby odezvy
transakcí. Vztah jednotlivých protokolů k těmto dvěma kritickým
faktorům ukazuje následující schéma:



Indexování

Indexy jako vyhledávací technika nejsou nic nového. Nedávné
archeologické průzkumy dokonce naznačují, že v jisté podobě je
využíval již člověk neandrtálský. V oblasti výpočetní techniky
se zavedly jako prostředek pro snížení počtu diskových I/O
operací (a tedy zkrácení doby odezvy) a byly používány v
několika souborových systémech ještě před nástupem relačních
databází.

Primárním důvodem (ne však jediným) pro vytváření indexu je
zlepšení výkonnosti. Druhý důvod má co do činění s dosažením
unikátnosti mezi řádkami uloženými v databázové tabulce. Tabulky
v relační databázi jsou zpravidla navrženy s primárním klíčem.
Pakliže je do tabulky definované s primárním klíčem vložena nová
řádka, je na SŘBD, aby zajistil, že hodnota primárního klíče pro
tuto řádku je unikátní. V případě, že by SŘBD musela pokaždé,
když se vkládá nový řádek procházet celou tabulku, byla by
výkonnost velmi špatná. Vhodným řešením proto je vytvořit
unikátní index nad primárním klíčem a nechat jej, aby ho SŘBD
využívala jako donucovacího mechanismu pro zajištění unikátnosti
řádek.

Kdy vytvořit a kdy nevytvořit index

Zatímco je takřka povinností vytvořit unikátní index nad
primárním klíčem tabulky, existují i jiné situace, kdy je
použití indexu vhodné. Je ho možné např. využít nad cizím
klíčem. Jelikož je cizí klíč vždy joinován s primárním klíčem
druhé tabulky, bylo by sice možné pro urychlení operace join
využít unikátní index nad primárním klíčem druhé tabulky,
nicméně z důvodů výkonnosti a sdíleného přístupu je vhodné dát
SŘBD možnost použití indexu nad cizím klíčem, a to i v případě,
kdy již existuje index nad primárním klíčem v referenční
tabulce. V praxi se totiž hodnoty cizího klíče objevují jako
omezovací kritéria ve WHERE klauzuli, takže lze využít i
příslušný index.

Index by se dále měl použít i tehdy, když se některé sloupce
často vyskytují ve WHERE klauzuli, a to i v případě, že nejsou
součástí primárního ani cizího klíče. Čím více indexovaných
sloupců ve WHERE klauzuli je, tím více je i pravděpodobné, že
SŘBD bude schopná využít indexy pro zrychlení výkonnosti dotazu.

Existuje tu však i nevýhoda a jakési pravidlo: malý počet indexů
způsobuje pomalé zpracování dotazu, velký počet indexů na druhou
stranu způsobuje pomalé změny v databázi. To je cena, jakou je
třeba za indexy platit: zvýšený overhead, spojený s údržbou
indexů během provádění operací INSERT, UPDATE a DELETE. Cílem
tedy je najít "zlatou" střední cestu mezi oběma extrémy.

Jednou z možností, jak toho dosáhnout, je vyjít ze situací, ve
kterých by se indexy vytvářet neměly.

V první řadě by se neměly vytvářet nad malými tabulkami. Pakliže
jen několik málo diskových operací čtení dokáže prohledat celou
tabulku, pak index vlastně zpomalí celé zpracování, jelikož
vyžaduje minimálně jeden až dvě operace čtení navíc. Co se ale
považuje za malou tabulku? Velikostní limit se liší v závislosti
na hardwaru, operačním systému, SŘBD a velikosti řádky, nicméně
každá tabulka s méně jak 100 řádkami je vhodným kandidátem.

Za druhé, indexy by se neměly vytvářet nad sloupci, které
nabývají jen několika málo hodnot (mají malý definiční obor).
Nejlepším kandidátem pro vytvoření indexu jsou sloupce s
unikátními hodnotami. Naopak sloupce, jež nabývají jen několika
málo hodnot (např. "pohlaví") jsou kandidátem nejhorším. Pro
SŘBD je totiž daleko efektivnější v těchto případech projít
celou tabulku než použít index k nalezení 50 procent řádek.

Dále je třeba si uvědomit, že sloupce obsahující tzv. binary
large object (BLOB - také známé pod označením "long" nebo "raw"
sloupce) nelze indexovat, a že pro to existuje velmi pádný
důvod. Index nad takovým sloupcem by zabral daleko více
diskového prostoru než samotná tabulka a výkonnost by se tudíž
značně snížila. Efektivní přístup k BLOB datům je předmětem
intenzivního výzkumu.


Autor článku