Relační databáze
Relační schéma
Relační model databáze je založen na konceptu relací, které představují tabulky s daty. Relační schéma je plán, který definuje strukturu tabulek – jaké atributy obsahují, jaké mají datové typy a jak jsou mezi sebou propojené.
Základní pojmy relačního modelu
| Pojem | Význam |
|---|---|
| Relace (tabulka) | Množina dat organizovaná do sloupců a řádků. Matematicky: relace je podmnožina kartézského součinu domén. |
| Atribut (sloupec) | Charakteristika popisující vlastnosti entity. Má název a datový typ. |
| \(n\)-tice (řádek/záznam) | Jeden konkrétní záznam v tabulce – uspořádaná \(n\)-tice hodnot odpovídajících atributům. |
| Doména | Množina povolených hodnot pro daný atribut. Např. doména Vek = \(\{x \in \mathbb{N} \mid 0 \le x \le 120\}\). |
| Klíč | Atribut nebo sada atributů, které jednoznačně identifikují každý řádek. |
Schéma relace – zápis
Schéma relace zapisujeme jako \(R(A_1, A_2, \dots, A_n)\), kde \(R\) je název relace a \(A_i\) jsou atributy. Příklad:
Podtržený atribut označuje primární klíč.
Klíče
| Typ klíče | Význam | Příklad |
|---|---|---|
| Superklíč | Libovolná množina atributů, která jednoznačně identifikuje každý řádek. | \(\{RČ\}\), \(\{RČ, jméno\}\), \(\{email\}\) |
| Kandidátní klíč | Minimální superklíč – nelze z něj odebrat žádný atribut bez ztráty jednoznačnosti. | \(\{RČ\}\), \(\{email\}\) |
| Primární klíč | Jeden vybraný kandidátní klíč. V tabulce může být pouze jeden. Označuje se podtržením. | \(\underline{RČ}\) |
| Cizí klíč | Atribut(y) odkazující na primární klíč jiné (nebo stejné) tabulky. Slouží k vyjádření vztahů. | student_RČ → Student(RČ) |
| Složený klíč | Klíč tvořený více atributy – žádný z nich sám o sobě neidentifikuje řádek jednoznačně. | \(\{student\_RČ, kurz\_ID\}\) u přihlášek |
Vztahy mezi entitami
Při modelování vztahů mezi tabulkami řešíme dvě otázky: kolik entit se účastní vztahu (kardinalita) a zda musí se entita vztahu účastnit (parcialita).
Kardinalita
Kardinalita určuje maximální počet entit na obou stranách vztahu:
| Typ | Popis | Příklad |
|---|---|---|
| 1 : 1 | Jeden záznam entity A souvisí s maximálně jedním záznamem entity B a naopak. | Osoba ↔ Rodný list |
| 1 : N | Jeden záznam A souvisí s více záznamy B, ale B souvisí s maximálně jedním A. | Zákazník → Objednávky |
| M : N | Jeden záznam A souvisí s více B a naopak. Vyžaduje vazební tabulku. | Student ↔ Kurz (přes Přihlášky) |
Realizace vazby M : N
Vazba M : N se v relačním modelu realizuje vazební (spojovací) tabulkou, která obsahuje dva cizí klíče – jeden na každou stranu vazby. Tím se M : N rozloží na dvě vazby 1 : N.
Parcialita
Parcialita (participační omezení) určuje, zda je účast entity ve vztahu povinná nebo volitelná:
- Povinná účast (
||): Entita se musí vztahu účastnit. Každý záznam v tabulce A musí mít odpovídající záznam v tabulce B. V SQL: sloupec cizího klíče jeNOT NULL. - Volitelná účast (
|o): Entita se vztahu účastnit může, ale nemusí. Sloupec cizího klíče může býtNULL.
Příklad parciality a kardinality v ER diagramu
erDiagram
ZAMESTNANEC ||--o| PROJEKT : "má"
Zaměstnanec má 0 nebo 1 projekt (volitelná, max. 1), a projekt musí mít právě jednoho zaměstnance (povinná, max. 1).
Integritní omezení
Integritní omezení (IO) jsou pravidla, která zajišťují správnost, přesnost a konzistenci dat. Bez nich by databáze mohla obsahovat nesmyslná data – záporný věk, neexistující cizí klíče, nebo prázdné primární klíče.
Doménová integrita
Každá hodnota v daném sloupci spadá do definované domény – tedy má správný datový typ, spadá do povoleného rozsahu a splňuje případná další omezení.
CREATE TABLE Zamestnanci (
id INT PRIMARY KEY,
jmeno VARCHAR(100) NOT NULL,
vek INT CHECK (vek >= 18 AND vek <= 65),
email VARCHAR(100) UNIQUE NOT NULL,
plat DECIMAL(10,2) DEFAULT 0
);
Prostředky doménové integrity v SQL
| Omezení | Význam |
|---|---|
NOT NULL |
Hodnota nesmí být prázdná. |
UNIQUE |
Všechny hodnoty ve sloupci musí být navzájem různé. |
CHECK (podmínka) |
Hodnota musí splňovat zadanou logickou podmínku. |
DEFAULT hodnota |
Pokud hodnota není zadána, použije se implicitní. |
| Datový typ | Samotný typ (INT, DATE, BOOLEAN, …) definuje základní doménu. |
Entitní integrita
Každá tabulka musí mít primární klíč, který je jednoznačný a neprázdný (NOT NULL). Důsledek: v tabulce nemohou existovat dva řádky se stejným primárním klíčem a žádný řádek nesmí mít primární klíč NULL.
CREATE TABLE Studenti (
student_id INT PRIMARY KEY, -- entitní integrita: unikátní, NOT NULL
jmeno VARCHAR(100) NOT NULL
);
Referenční integrita
Cizí klíče musí odkazovat na existující hodnoty primárního klíče v referenční tabulce. Zajišťuje, že vztahy mezi tabulkami zůstávají konzistentní.
CREATE TABLE Prihlasky (
prihlaska_id INT PRIMARY KEY,
student_id INT NOT NULL,
kurz_id INT NOT NULL,
FOREIGN KEY (student_id) REFERENCES Studenti(student_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (kurz_id) REFERENCES Kurzy(kurz_id)
);
Akce při porušení referenční integrity
Co se stane, když se pokusíme smazat studenta, který má přihlášky?
| Akce | Chování |
|---|---|
ON DELETE CASCADE |
Smažou se i všechny přihlášky daného studenta. |
ON DELETE SET NULL |
Cizí klíč v přihláškách se nastaví na NULL. |
ON DELETE RESTRICT |
Smazání se odmítne, pokud existují přihlášky. |
ON DELETE SET DEFAULT |
Cizí klíč se nastaví na výchozí hodnotu. |
Rekurzivní a ternární vztahy
- Rekurzivní vztah – relace, ve které cizí klíč odkazuje na primární klíč té samé tabulky. Např.
Zamestnanec(nadrizeny_id → Zamestnanec(id)). - Ternární vztah – vztah mezi třemi entitami. Např.
Lékař–Pacient–Lék (předepisuje).
Návrh relační databáze
Návrh relační databáze je proces, ve kterém plánujeme, jakým způsobem budou data uložena, organizována a propojena. Správný návrh je klíčový pro efektivní a spolehlivé ukládání a manipulaci s daty.
Konceptuální schéma
Konceptuální schéma je model, který zobrazuje strukturu dat, jejich vlastnosti a vztahy – bez ohledu na fyzickou implementaci. Vyjadřuje co chceme ukládat, ne jak. Nejčastěji se kreslí pomocí ER diagramů (Entity-Relationship).
Ukázka ER diagramu
erDiagram
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
CUSTOMER ||--o{ ORDER : places
CUSTOMER ||--o{ INVOICE : "liable for"
DELIVERY-ADDRESS ||--o{ ORDER : receives
INVOICE ||--|{ ORDER : covers
ORDER ||--|{ ORDER-ITEM : includes
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
Prvky konceptuálního schématu
- Entity – objekty, které mají být uloženy v databázi (Student, Kurz, Učitel).
- Atributy – vlastnosti entit (jméno, datum narození, kapacita).
- Vztahy – vazby mezi entitami (Student je zapsán do Kurzu).
- Primární klíč – jednoznačná identifikace entity.
- Cizí klíč – odkaz na primární klíč jiné entity.
Od konceptuálního k relačnímu schématu
Proces transformace konceptuálního modelu na relační schéma se řídí těmito kroky:
- Přizpůsobit konceptuální schéma požadavkům relačního modelu.
- Transformovat entity na tabulky, atributy na sloupce a vztahy na cizí klíče (případně vazební tabulky).
- Normalizovat relační schéma (odstranit redundanci).
- Validovat schéma vůči požadovaným transakcím.
- Konzultovat, zda návrh odpovídá zadání.
Bottom-up přístup
Cílem bottom-up přístupu je seskupování atributů do relací tak, aby byly informace uloženy na „nejlepším možném místě". Vycházíme od jednotlivých atributů a postupně je sdružujeme podle logické sounáležitosti a funkčních závislostí.
Aktualizační anomálie
Aktualizační anomálie jsou nežádoucí vedlejší efekty operací nad databází, které vedou k nekonzistenci nebo ztrátě dat. Vznikají v důsledku redundance – opakování stejných dat na více místech.
| Anomálie | Kdy nastává | Příklad |
|---|---|---|
| Vkládací | Pro vložení nového záznamu je nutné zadat redundantní data, nebo vložení nelze provést vůbec. | Nelze zapsat nový kurz, pokud na něj ještě není přihlášen žádný student. |
| Modifikační | Pro aktualizaci jedné informace je nutné změnit více záznamů. | Změna názvu kurzu vyžaduje přepsat všechny řádky, kde se kurz vyskytuje. |
| Mazací | Smazání záznamu způsobí ztrátu souvisejících dat. | Smazání posledního studenta z kurzu smaže i informaci o kurzu samotném. |
Řešení – normalizace
Aktualizační anomálie se řeší pomocí normalizace – procesu dekompozice tabulek na menší, které splňují normální formy a eliminují redundanci.
Normální formy
Normalizace je systematický proces dekompozice relačního schématu tak, aby se eliminovala redundance a s ní spojené anomálie. Postupuje se po normálních formách – každá vyšší forma splňuje všechny požadavky forem nižších.
Přehled normálních forem
| Forma | Hlavní požadavek | Co odstraňuje |
|---|---|---|
| 1NF | Všechny atributy jsou atomické. | Vícehodnotové atributy, opakující se skupiny. |
| 2NF | Žádné částečné závislosti neklíčových atributů na složeném klíči. | Částečné závislosti (netýká se tabulek s jednoduchým PK). |
| 3NF | Žádné tranzitivní závislosti neklíčových atributů. | Tranzitivní závislosti. |
| BCNF | Každá netriviální funkční závislost \(X \to Y\) má \(X\) jako superklíč. | Anomálie přežívající 3NF u tabulek s více kandidátními klíči. |
První normální forma (1NF)
Podmínky 1NF
Relace je v první normální formě, pokud:
- Každý atribut obsahuje pouze atomické (dále nedělitelné) hodnoty.
- Relace neobsahuje vícehodnotové atributy (žádné pole, seznamy, vnořené tabulky v jednom sloupci).
- Všechny \(n\)-tice mají stejný počet atributů.
Příklad – převod do 1NF
Relace \(Studenti(RČ, jméno, příjmení, adresa, ocenění)\):
adresanení atomická – obsahuje ulici, město a PSČ.oceněníje vícehodnotové – student může mít více ocenění.
Řešení: Rozložíme na atomické atributy a vícehodnotový atribut přesuneme do samostatné relace:
- \(Studenti(RČ, jméno, příjmení, ulice, město, PSČ)\)
- \(Ocenění(RČ, ocenění)\)
Druhá normální forma (2NF)
Podmínky 2NF
Relace je v druhé normální formě, pokud:
- Je v 1NF.
- Každý neklíčový atribut je plně funkčně závislý na celém primárním klíči – nikoliv jen na jeho části.
Týká se pouze tabulek se složeným primárním klíčem. Pro tabulky s jednoduchým primárním klíčem je splnění 1NF automaticky i 2NF.
Příklad – porušení 2NF a převod
\(Přihlášky(RČ, jméno, příjmení, univerzita, adresa\_univerzity)\) s primárním klíčem \(\{RČ, univerzita\}\):
jménoapříjmenízávisí pouze na \(RČ\) (části klíče) – částečná závislost.adresa_univerzityzávisí pouze nauniverzita– taky částečná závislost.
Řešení: Rozložíme na relace, kde jsou neklíčové atributy plně závislé na svém PK:
- \(Přihlášky(RČ, univerzita)\)
- \(Lidé(RČ, jméno, příjmení)\)
- \(Univerzity(univerzita, adresa\_univerzity)\)
Třetí normální forma (3NF)
Podmínky 3NF
Relace je v třetí normální formě, pokud:
- Je v 2NF.
- Každý neklíčový atribut je přímo závislý na klíči – nesmí existovat tranzitivní závislost (neklíčový atribut závisí na jiném neklíčovém atributu).
Příklad – tranzitivní závislost
\(Zaměstnanci(id, jméno, oddělení\_id, název\_oddělení)\):
- \(id \to oddělení\_id\) (klíč → neklíčový)
- \(oddělení\_id \to název\_oddělení\) (neklíčový → neklíčový) ← tranzitivní závislost
Řešení: Přesuneme tranzitivně závislý atribut do samostatné relace:
- \(Zaměstnanci(id, jméno, oddělení\_id)\)
- \(Oddělení(oddělení\_id, název\_oddělení)\)
Boyce-Coddova normální forma (BCNF)
Podmínky BCNF
Relace je v BCNF, pokud:
- Je v 3NF.
- Pro každou netriviální funkční závislost \(X \to Y\) platí, že \(X\) je superklíč.
BCNF je striktnější než 3NF – řeší anomálie, které 3NF ponechává v případech, kdy tabulka obsahuje více překrývajících se kandidátních klíčů. V praxi je většina 3NF schémat i BCNF.
Bezztrátová dekompozice
Dekompozice je proces rozdělení jedné relace na dvě nebo více menších relací tak, aby výsledné schéma eliminovalo redundanci a anomálie. Klíčová otázka zní: ztratíme při rozdělení nějaká data? Pokud ano, je to ztrátová dekompozice – a to je nepřípustné.
Bezztrátová dekompozice – definice
Mějme relaci \(R\) s množinou funkčních závislostí \(F\). Dekompozice \(R\) na \(R_1\) a \(R_2\) je bezztrátová právě tehdy, když pro každou instanci \(r\) relace \(R\) platí:
Jinými slovy: přirozené spojení projekcí na \(R_1\) a \(R_2\) dá přesně původní relaci – žádné řádky nepřibyly, žádné nezmizely.
Jak ověřit, že je dekompozice bezztrátová?
Dekompozice \(R\) na \(R_1\) a \(R_2\) je bezztrátová, pokud platí alespoň jedna z následujících podmínek:
- \(R_1 \cap R_2 \to R_1\) – průnik atributů obou relací je superklíčem v \(R_1\).
- \(R_1 \cap R_2 \to R_2\) – průnik atributů obou relací je superklíčem v \(R_2\).
Prakticky: společné atributy obou rozložených relací musí tvořit nadklíč alespoň v jedné z nich. To znamená, že podle společných atributů lze jednoznačně dohledat zbytek.
Příklad – bezztrátová dekompozice
Mějme relaci \(Student(RČ, jméno, příjmení, email)\) s primárním klíčem \(\underline{RČ}\).
Dekompozice:
- \(R_1(RČ, jméno, příjmení)\)
- \(R_2(RČ, email)\)
Ověření: \(R_1 \cap R_2 = \{RČ\}\) – a \(RČ\) je superklíčem v \(R_1\) i \(R_2\). → Bezztrátová.
Proč? Když \(R_1 \bowtie R_2\) spojíme zpět přes \(RČ\), dostaneme přesně původní řádky – pro každého studenta se správně spáruje jméno s emailem.
Příklad – ztrátová dekompozice
Mějme relaci \(Výuka(učitel, předmět, učebna)\). Předpokládejme, že jeden učitel může učit více předmětů a jeden předmět může být v různých učebnách.
Chybná dekompozice:
- \(R_1(učitel, předmět)\)
- \(R_2(předmět, učebna)\)
Ověření: \(R_1 \cap R_2 = \{předmět\}\), ale \(předmět\) není superklíč v \(R_1\) ani \(R_2\) (učitel může učit více předmětů, předmět může být ve více učebnách). → Ztrátová!
Důsledek: Po spojení \(R_1 \bowtie R_2\) vzniknou falešné řádky – všichni učitelé daného předmětu budou spárováni se všemi učebnami, kde se předmět učí, i když tam ve skutečnosti nikdy neučili.
Rekonstrukce – správná dekompozice
Aby byla dekompozice bezztrátová, musí jedna z rozložených relací obsahovat klíč původní relace. Správná dekompozice:
- \(R_1(učitel, předmět)\)
- \(R_2(učitel, učebna)\)
\(R_1 \cap R_2 = \{učitel\}\) – a pokud \(učitel \to předmět\) platí v \(R_1\), pak je dekompozice bezztrátová.
Indexy
Index je datová struktura, která zrychluje vyhledávání záznamů v tabulce. Místo sekvenčního procházení všech řádků se dotaz podívá do indexu, který funguje jako rejstřík – rychle najde ukazatel na data a záznam vrátí.
Jak indexy fungují
Index obsahuje klíč (hodnoty indexovaného atributu nebo kombinace atributů) a ukazatel na fyzické umístění záznamu v tabulce. Interně používá vyvážené stromové struktury (typicky \(B^+\)-strom) nebo hashovací tabulky.
Cena za rychlost: zápis (INSERT, UPDATE, DELETE) musí kromě tabulky aktualizovat i všechny indexy.
-- Vytvoření indexu
CREATE INDEX idx_prijmeni ON Studenti(prijmeni);
-- Unikátní index
CREATE UNIQUE INDEX idx_email ON Studenti(email);
-- Složený index
CREATE INDEX idx_jmeno_prijmeni ON Studenti(jmeno, prijmeni);
-- Smazání indexu
DROP INDEX idx_prijmeni;
Typy indexů – přehled
| Typ | Popis | Výhody | Nevýhody |
|---|---|---|---|
| Úzký | Definovaný nad jedním atributem. | Rychlejší pro jednoduché podmínky, menší režie údržby. | Nepomůže u dotazů filtrujících podle více sloupců. |
| Široký (složený) | Definovaný nad více atributy. | Efektivní pro dotazy s více podmínkami, podporuje covering queries. | Větší velikost, vyšší režie při zápisu. |
| Hustý | Odkazuje přímo na konkrétní záznam (každý řádek má položku v indexu). | Přesné, rychlé pro point queries. | Větší velikost indexu. |
| Řídký | Odkazuje na stránku/blok, kde se záznam nachází. | Menší velikost, rychlejší údržba. | Méně přesný – vyžaduje dohledání na stránce. |
| Primární | Index nad primárním klíčem – vytváří se automaticky. | Garantuje jedinečnost PK, rychlé vyhledávání podle PK. | Nelze odstranit bez odstranění PK. |
| Sekundární | Index nad neklíčovým atributem. | Zrychluje dotazy podle jiných kritérií než PK. | Režie při zápisu – nutno udržovat. |
| Unikátní | Vynucuje UNIQUE – žádné dvě hodnoty nejsou stejné. |
Garance unikátnosti na úrovni DB, ne v aplikaci. | Zpomaluje vkládání (nutnost kontroly). |
Klastrovaný vs. neklastrovaný index
Toto je zásadní architektonické rozhodnutí – týká se fyzického uspořádání dat na disku:
Klastrovaný index (Clustered)
Data v tabulce jsou fyzicky seřazena podle hodnot indexovaného atributu. Listové uzly \(B^+\)-stromu obsahují přímo celé řádky tabulky.
- Výhody: Extrémně rychlé pro range queries (
BETWEEN,ORDER BY), data čtena sekvenčně z disku, covering queries. - Nevýhody: Pouze jeden na tabulku (data lze fyzicky seřadit jen jedním způsobem). Při zápisu se musí přesouvat data, nejen index – dražší INSERT/UPDATE. Vhodný pro sloupce, podle kterých se často řadí nebo se dotazuje na rozsahy.
- Případ použití: Primární klíč (většina DB ho automaticky dělá klastrovaným), datum vytvoření u logovacích tabulek.
Neklastrovaný index (Unclustered)
Index vytváří samostatnou strukturu, která obsahuje jen klíč a ukazatel na řádek v tabulce. Fyzické pořadí dat se nemění.
- Výhody: Lze jich mít více na jedné tabulce (typicky desítky). Menší režie při zápisu – přesouvá se jen struktura indexu, ne data. Vhodný pro časté vyhledávací podmínky.
- Nevýhody: Pomalejší pro range queries – ukazatele odkazují na data rozptýlená po disku. Dva kroky: index → ukazatel → data.
- Případ použití: Cizí klíče, často filtrované sloupce (email, stav objednávky), fulltextové vyhledávání.
Strategie indexování
Kdy indexovat a kdy ne
| Faktor | Indexovat | Neindexovat |
|---|---|---|
| Selektivita | Vysoká – mnoho různých hodnot (email, RČ). | Nízká – málo hodnot (pohlaví, boolean). |
| Četnost dotazů | Často filtrované nebo JOINované sloupce. | Zřídka používané. |
| Poměr čtení/zápis | Hodně čtení, málo zápisů. | Hodně zápisů, málo čtení. |
| Velikost tabulky | Velké tabulky – sekvenční scan je drahý. | Malé tabulky – sekvenční scan je rychlý. |
| Pořadí ve složeném indexu | Nejdřív atributy v podmínkách WHERE, pak JOIN, nakonec ORDER BY. |
Nahodilé pořadí. |
Přeindexovanost
Příliš mnoho indexů škodí:
- Každý zápis musí aktualizovat všechny indexy.
- Indexy zabírají místo na disku.
- Query optimizer může zvolit suboptimální index.
Transakce
Transakce je posloupnost SQL příkazů, která převádí databázi z jednoho konzistentního stavu do druhého. Funguje jako atomická jednotka – buď se provede celá, nebo vůbec. Bez transakčního zpracování by souběžný přístup více uživatelů vedl k chaosu – jeden by četl data uprostřed změn druhého, převody peněz by mizely, rezervace by se duplikovaly.
ACID
ACID je akronym čtyř vlastností, které každá transakce garantuje. Databázový systém je vynucuje kombinací zámků, logování a správy verzí.
Čtyři vlastnosti ACID
Atomicita (Atomicity) : Transakce je nedělitelná – buď se provedou všechny její operace, nebo žádná. Selhání uprostřed (výpadek proudu, chyba aplikace, porušení IO) → automatický rollback všeho, co se do té doby provedlo. Atomicita se implementuje pomocí undo logu – databáze si před každou změnou uloží původní hodnotu, aby ji mohla v případě rollbacku obnovit.
Konzistence (Consistency)
: Transakce převádí databázi z jednoho konzistentního stavu do druhého. Konzistentní stav = žádné integritní omezení (PK, FK, CHECK, UNIQUE) není porušeno. Pokud transakce uprostřed poruší IO (např. INSERT s duplicitním PK), je odmítnuta. Konzistence je odpovědností programátora i DBMS – DB zajistí IO, aplikační logiku musí hlídat vývojář.
Izolovanost (Isolation)
: Souběžné transakce se navzájem neovlivňují – každá vidí databázi, jako by běžela sama. Dokonalá izolace (SERIALIZABLE) je drahá (zámkování), proto standard SQL definuje slabší úrovně, které povolují určité konflikty výměnou za vyšší výkon. Viz stupně izolace níže.
Trvanlivost (Durability)
: Úspěšně potvrzená transakce (COMMIT) je trvale uložena – přežije výpadek proudu, restart serveru i pád disku. Implementuje se pomocí redo logu (WAL – Write-Ahead Log): před zapsáním změn do datových souborů se změna zapíše do logu na disk. Při restartu po výpadku se z logu obnoví všechny committed transakce.
-- Atomický převod peněz: oba UPDATE, nebo nic
BEGIN TRANSACTION;
UPDATE Ucty SET zustatek = zustatek - 1000 WHERE id = 1;
UPDATE Ucty SET zustatek = zustatek + 1000 WHERE id = 2;
COMMIT;
-- Pokud cokoliv selže:
-- ROLLBACK;
Konflikty
Když běží více transakcí současně a nejsou dostatečně izolované, vznikají následující problémy. Každý z nich reprezentuje jiný typ nekonzistence:
Dirty Read (špinavé čtení)
Transakce \(T_1\) čte data, která transakce \(T_2\) zapsala, ale ještě nepotvrdila (COMMIT). Pokud se \(T_2\) následně vrátí (ROLLBACK), \(T_1\) pracovala s daty, která ve skutečnosti nikdy neexistovala.
| Čas | Transakce A | Transakce B |
|---|---|---|
| 1 | BEGIN |
|
| 2 | UPDATE zustatek = 500 WHERE id = 1 |
|
| 3 | SELECT zustatek FROM Ucty WHERE id = 1 – vidí 500 |
|
| 4 | ROLLBACK – vrací původních 1000 |
|
| 5 | Transakce B pracovala s hodnotou 500, která nikdy neexistovala |
Non-repeatable Read (neopakovatelné čtení)
Transakce \(T_1\) čte stejný řádek dvakrát, ale mezi tím jiná transakce \(T_2\) hodnotu změnila a potvrdila. \(T_1\) podruhé vidí jinou hodnotu.
| Čas | Transakce A | Transakce B |
|---|---|---|
| 1 | BEGIN |
|
| 2 | SELECT zustatek FROM Ucty WHERE id = 1 – vidí 1000 |
|
| 3 | UPDATE Ucty SET zustatek = 500 WHERE id = 1; COMMIT |
|
| 4 | SELECT zustatek FROM Ucty WHERE id = 1 – vidí 500! |
|
| 5 | Transakce A viděla dvě různé hodnoty pro tentýž řádek |
Phantom Read (fantómové čtení)
Transakce \(T_1\) provede dotaz se stejnou podmínkou dvakrát, ale mezi tím jiná transakce \(T_2\) vložila nový řádek (nebo smazala existující), který podmínce vyhovuje. \(T_1\) podruhé vidí řádky, které předtím neexistovaly.
| Čas | Transakce A | Transakce B |
|---|---|---|
| 1 | BEGIN |
|
| 2 | SELECT COUNT(*) FROM Objednavky WHERE stav = 'nova' – 10 |
|
| 3 | INSERT INTO Objednavky (...) VALUES (...); COMMIT |
|
| 4 | SELECT COUNT(*) FROM Objednavky WHERE stav = 'nova' – 11! |
|
| 5 | Transakce A vidí nový řádek, který předtím neexistoval |
Lost Update (ztracená aktualizace)
Dvě transakce čtou stejná data, každá je upraví a zapíše zpět – druhá přepíše změny první, aniž by o nich věděla.
| Čas | Transakce A | Transakce B |
|---|---|---|
| 1 | SELECT zustatek FROM Ucty WHERE id = 1 – 1000 |
|
| 2 | SELECT zustatek FROM Ucty WHERE id = 1 – 1000 |
|
| 3 | UPDATE Ucty SET zustatek = 500 WHERE id = 1 |
|
| 4 | UPDATE Ucty SET zustatek = 1200 WHERE id = 1; COMMIT |
|
| 5 | COMMIT – ale B už přepsal změnu A! |
Zůstatek je 500 (co napsala A), přestože B provedl COMMIT později. Záleží na implementaci, ale obě transakce přepsaly práci té druhé.
Stupně izolace
Standard SQL definuje čtyři úrovně izolace, které představují kompromis mezi konzistencí a výkonem. Vyšší izolace = méně konfliktů, ale více zámků a nižší propustnost.
| Úroveň | Dirty Read | Non-repeatable Read | Phantom Read | Lost Update |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ Možný | ✅ Možný | ✅ Možný | ✅ Možný |
| READ COMMITTED | ❌ | ✅ Možný | ✅ Možný | ✅ Možný |
| REPEATABLE READ | ❌ | ❌ | ✅ Možný | ❌ |
| SERIALIZABLE | ❌ | ❌ | ❌ | ❌ |
READ UNCOMMITTED
Nejslabší úroveň – transakce může číst data, která jiná transakce ještě nepotvrdila (dirty read). Prakticky žádná izolace.
- Jak to funguje: SELECT nebere žádné zámky, ignoruje zámky ostatních.
- Výhody: Maximální výkon, žádné blokování.
- Nevýhody: Prakticky nepoužitelné pro cokoliv, kde záleží na správnosti dat.
- Použití: Pouze pro přibližné agregace, kde na přesnosti nezáleží (např. odhad počtu návštěvníků za poslední minutu).
READ COMMITTED
Transakce vidí pouze data potvrzená (COMMIT) jinými transakcemi. Dirty read není možný. Většina databází (PostgreSQL, Oracle, SQL Server) používá tuto úroveň jako výchozí.
- Jak to funguje: Každý SELECT vidí snapshot dat platný v okamžiku spuštění dotazu (ne transakce!).
- Výhody: Slušný kompromis – žádné dirty ready, ale stále vysoká propustnost. V PostgreSQL se používá MVCC – writers neblokují readers.
- Nevýhody: Non-repeatable read a phantom read jsou možné – stejný SELECT uvnitř jedné transakce může vrátit různé výsledky.
- Použití: OLTP systémy s vysokou propustností (e-shopy, bankovní dotazy na zůstatek).
REPEATABLE READ
Transakce vidí stejná data po celou dobu svého trvání – pokud přečte řádek na začátku transakce, každé další čtení stejného řádku vrátí stejnou hodnotu. Dirty read ani non-repeatable read nejsou možné.
- Jak to funguje: Při prvním čtení řádku se na něj vezme zámek (nebo se v MVCC zaznamená verze snapshotu platná pro celou transakci).
- Výhody: Konzistentní pohled na data uvnitř transakce – vhodné pro operace, které vyžadují více čtení v konzistentním stavu.
- Nevýhody: Phantom read je stále možný – nové řádky splňující podmínku WHERE se mohou objevit. Více zámků → menší propustnost.
- V MySQL InnoDB: REPEATABLE READ je výchozí úroveň a díky gap lockům blokuje i phantom ready – chová se téměř jako SERIALIZABLE.
SERIALIZABLE
Nejpřísnější úroveň – transakce se chovají, jako by běžely sériově (jedna po druhé), i když ve skutečnosti běží souběžně. Žádné konflikty nejsou možné.
- Jak to funguje: DB používá kombinaci zámků, predikátních zámků (zamyká i rozsahy hodnot, ne jen existující řádky), nebo detekci sériových konfliktů a abort transakcí (SSI – Serializable Snapshot Isolation v PostgreSQL).
- Výhody: Úplná izolace – programátor nemusí řešit žádné souběhové jevy.
- Nevýhody: Nízká propustnost, časté aborty transakcí při konfliktu. Nejhůř škáluje.
- Použití: Finanční systémy, kde je správnost kritická (převody mezi bankami, účetní uzávěrky).
Jak se izolace implementuje – zámky vs. MVCC
| Mechanismus | Princip | Výhody | Nevýhody | Používá |
|---|---|---|---|---|
| Pesimistické zámky (Pessimistic Locking) | Před čtením/zápisem se vezme zámek (shared/exclusive). Ostatní čekají. | Jednoduchý model, garantovaná konzistence. | Nízká propustnost – readeři blokují writery a naopak. | SQL Server (výchozí), starší Oracle. |
| Optimistické zámky (Optimistic Locking) | Transakce čte bez zámků, při COMMITu ověří, že data nebyla změněna (např. porovnáním verze). Pokud byla → abort. | Vysoká propustnost při malém konfliktu, ideální pro read-heavy systémy. | Při vysokém konfliktu mnoho abortů a retry. | Aplikační vrstva (JPA @Version, Hibernate optimistic). |
| MVCC (Multi-Version Concurrency Control) | Každá transakce vidí snapshot databáze v okamžiku svého startu. Writers vytvářejí nové verze řádků, readers čtou staré verze. Readers nikdy neblokují writers a naopak. | Vysoká propustnost, ideální pro mixed workloads. | Složitější implementace, režie úklidu starých verzí (VACUUM). | PostgreSQL, MySQL InnoDB, Oracle, SAP HANA. |
MVCC v PostgreSQL – jak to funguje
Každý řádek v PostgreSQL nese metadata o tom, která transakce ho vytvořila a která ho smazala. Při SELECTu transakce vidí jen řádky, které:
- Byly vytvořeny transakcí, která už commitnula před startem mé transakce.
- Nebyly smazány transakcí, která commitnula před startem mé transakce.
Takže každá transakce pracuje se stabilním snapshotem – bez zámků, bez čekání. Writers mezi sebou soupeří jen o tentýž řádek.
Praktická volba stupně izolace
| Scénář | Doporučená úroveň | Proč |
|---|---|---|
| Zobrazení zůstatku účtu | READ COMMITTED | Stačí vidět poslední potvrzený stav. |
| Převod peněz mezi účty | REPEATABLE READ | Musí vidět konzistentní zůstatky obou účtů po celou dobu transakce. |
| Generování měsíční účetní uzávěrky | SERIALIZABLE | Nesmí se objevit žádné nové transakce během výpočtu sum. |
| Aktuální počet online uživatelů | READ UNCOMMITTED | Orientační údaj, přesnost není kritická. |
Triggers a uložené procedury
Triggers
Trigger je procedura uložená na databázovém serveru, která se automaticky spouští při definované události na tabulce.
CREATE TRIGGER jmeno
BEFORE | AFTER | INSTEAD OF udalost ON tabulka
[REFERENCING referencni_promenne]
[FOR EACH ROW | FOR EACH STATEMENT]
WHEN (podminka)
akce
Načasování a granularita triggeru
- BEFORE – spouští se před provedením příkazu. Vhodné pro validaci dat, doplnění chybějících hodnot.
- AFTER – spouští se po provedení. Vhodné pro aplikační logiku, auditní logy, kaskádové akce.
- FOR EACH STATEMENT – jednou pro celý SQL příkaz.
- FOR EACH ROW – pro každý modifikovaný řádek zvlášť (pokud je modifikováno 0 řádků, trigger se nespustí).
ON DELETE CASCADE pomocí triggeru
CREATE TRIGGER tr_cascade
AFTER DELETE ON Studenti
REFERENCING old_row AS o
FOR EACH ROW
DELETE FROM Prihlasky WHERE Prihlasky.student_id = o.student_id;
Uložené procedury
Uložená procedura je blok kódu uložený a spouštěný přímo na databázovém serveru. Používá se pro zapouzdření často používaných příkazů, validaci dat a přesun části logiky do DB.
CREATE PROCEDURE povys_plat(IN zam_id INT, IN zvyseni DECIMAL)
BEGIN
UPDATE Zamestnanci
SET plat = plat + zvyseni
WHERE id = zam_id;
END;
-- Volání
EXECUTE povys_plat(42, 5000);
Pohledy
Pohled (View) je virtuální relace – uložený SQL dotaz, ke kterému se přistupuje jako k tabulce. Data fyzicky neexistují na disku, vznikají dynamicky při dotazu.
CREATE VIEW aktivni_studenti AS
SELECT s.id, s.jmeno, s.prijmeni, p.nazev AS obor
FROM Studenti s
JOIN Prihlasky pr ON s.id = pr.student_id
JOIN Programy p ON pr.program_id = p.id
WHERE pr.stav = 'aktivni';
-- Dotaz do pohledu (stejně jako do tabulky)
SELECT * FROM aktivni_studenti WHERE obor = 'IT';
Vlastnosti pohledů
- Skrývají složitost dotazu – uživatel se ptá jednoduchého SELECT.
- Skrývají citlivá data – pohled neobsahuje sloupce, které uživatel nemá vidět.
- Zjednodušují řízení práv –
GRANT SELECT ON pohledmísto na celou tabulku. WITH CHECK OPTION– zajistí, že modifikace přes pohled neporuší podmínku WHERE pohledu. Při pokusu o vložení řádku, který by v pohledu nebyl vidět, DB vyhodí výjimku.
SQL – přehled
Structured Query Language je deklarativní jazyk pro přístup a řízení relačních databází. Dělí se na několik podsad:
| Podmnožina | Význam | Klíčové příkazy |
|---|---|---|
| DQL – Data Query Language | Dotazování a výběr dat. | SELECT, FROM, WHERE, JOIN, GROUP BY, HAVING, ORDER BY |
| DML – Data Manipulation Language | Manipulace s daty. | INSERT, UPDATE, DELETE |
| DDL – Data Definition Language | Definice struktury databáze. | CREATE TABLE/INDEX/VIEW, ALTER TABLE, DROP, TRUNCATE |
| DCL – Data Control Language | Řízení přístupových práv. | GRANT, REVOKE |
| TCL – Transaction Control Language | Řízení transakcí. | COMMIT, ROLLBACK, SAVEPOINT |
DDL – vytvoření tabulky včetně integritních omezení
CREATE TABLE Teachers
(
id_teacher INTEGER NOT NULL,
id_office INTEGER NOT NULL,
email NVARCHAR(30) NOT NULL UNIQUE,
name NVARCHAR(30) NOT NULL,
surname NVARCHAR(30) NOT NULL,
title_before NVARCHAR(10) NULL,
title_after NVARCHAR(10) NULL,
dob DATETIME NOT NULL,
type INTEGER NOT NULL CHECK (type > 0 AND type < 6),
PRIMARY KEY (id_teacher),
CONSTRAINT fk_TeachOffice FOREIGN KEY (id_office)
REFERENCES Offices(id_office),
CONSTRAINT uc_NameSurnameDob UNIQUE (name, surname, dob)
);