Przejdź do treści
INF.03 · Dział III

Projektowanie baz danych

Model relacyjny i DBMS, klucze podstawowe, kandydujące i obce, integralność encji i referencyjna, relacje 1:1, 1:N i N:M, diagram ER, normalizacja do 3NF krok po kroku, typy danych MySQL, indeksy i ACID.

Wstęp: projekt bazy powstaje na kartce, nie w SQL-u

W części pisemnej ten dział daje pytania o klucze, typy relacji i postacie normalne. W części praktycznej dostajesz opis firmy albo gotowy diagram ER i masz z niego zrobić działającą bazę: tabele, klucze obce, typy kolumn. Nikt nie pyta „czym jest baza danych". Pyta, czy tabela jest w 2NF, ile tabel potrzeba na relację wiele-do-wielu i co się stanie z wypożyczeniami po usunięciu klienta. Cały ten artykuł prowadzimy na jednym przykładzie — bazie wypożyczalni filmów — żeby normalizację dało się zobaczyć, a nie tylko wykuć.

1

Baza danych, DBMS i model relacyjny

Egzamin oddziela dwie rzeczy, które w mowie potocznej się zlewają: bazę danych (zbiór danych) od systemu zarządzania bazą danych (program, który ten zbiór obsługuje). MySQL nie jest bazą danych — MySQL jest DBMS-em, a baza to wypozyczalnia, którą w nim utworzysz.

Baza danych

Uporządkowany zbiór danych o określonej strukturze, zapisany trwale i opisany własnym schematem. Struktura jest tu słowem kluczowym: plik tekstowy z notatkami bazą nie jest, bo bez zgadywania nie da się w nim odróżnić jednego pola od drugiego.

DBMS (SZBD)

Database Management System, po polsku system zarządzania bazą danych. Oprogramowanie pośredniczące między użytkownikiem a plikami danych: wykonuje zapytania, pilnuje więzów, zarządza uprawnieniami, obsługuje transakcje i kopie zapasowe. Przykłady: MySQL, MariaDB, PostgreSQL, Microsoft SQL Server, Oracle Database, SQLite.

Słownik: teoria kontra praktyka

Model relacyjny opisał w 1970 roku Edgar F. Codd i użył terminów matematycznych. W SQL-u te same rzeczy nazywają się inaczej. Egzamin używa obu zestawów zamiennie, więc trzeba je umieć sparować:

Termin teoretycznyTermin praktycznyPrzykład z wypożyczalni
relacjatabelatabela filmy
krotkarekord, wierszjeden konkretny film
atrybutpole, kolumnakolumna tytul
dziedzinatyp danych wraz z zakresem dopuszczalnych wartościrok produkcji: liczba całkowita z przedziału 1895–2100
stopień relacjiliczba kolumn5 kolumn = stopień 5
liczność (kardynalność)liczba wierszy840 filmów w katalogu

Pułapka słowna, na której łatwo stracić punkt

W teorii Codda relacja to tabela. W codziennej mowie „relacja" to związek między tabelami, np. jeden-do-wielu. To dwa różne znaczenia tego samego słowa. Gdy w zadaniu pada „relacja 1:N", chodzi o związek. Gdy pada „relacyjny model danych" albo „schemat relacji" — chodzi o tabelę. Rozstrzyga kontekst: jeśli obok stoi dwukropek z literami, mowa o związku.

Trzy właściwości poprawnej tabeli relacyjnej
  • Brak duplikatów wierszy. Każdy wiersz musi być odróżnialny od pozostałych — gwarantuje to klucz podstawowy.
  • Kolejność nie ma znaczenia. Ani kolejność wierszy, ani kolumn nie niesie informacji. Jeśli potrzebujesz „pierwszego" rekordu, musisz mieć kolumnę, po której da się posortować.
  • Wartość w komórce jest atomowa. Jedna komórka = jedna wartość. To już jest warunek pierwszej postaci normalnej.

2

Klucze i integralność danych

Klucz to kolumna albo zestaw kolumn pełniący rolę identyfikatora lub odsyłacza. Nazw jest kilka i różnią się jednym słowem, więc łatwo je pomylić — a zadanie „wskaż klucz obcy w podanym schemacie" to pewne punkty, jeśli wiesz, czego szukać.

Klucz podstawowy (PRIMARY KEY)

Jednoznacznie identyfikuje wiersz. W tabeli może być tylko jeden. Musi być unikalny i nie może zawierać NULL. Automatycznie tworzy indeks.

Klucz kandydujący

Każda kolumna lub zestaw kolumn, który mógłby pełnić rolę klucza podstawowego, bo jest unikalny i minimalny. W tabeli klientów kandydatami są PESEL, numer dowodu i adres e-mail. Jednego wybierasz na podstawowy, pozostałym zwykle nadajesz UNIQUE.

Klucz obcy (FOREIGN KEY)

Kolumna wskazująca na klucz podstawowy innej tabeli. W jednej tabeli może być ich wiele. Może przyjmować NULL, gdy powiązanie jest opcjonalne, i może się powtarzać — to właśnie on realizuje stronę „wiele" relacji.

Klucz złożony

Klucz podstawowy zbudowany z dwóch lub więcej kolumn, bo żadna pojedyncza nie identyfikuje wiersza. Typowe miejsce: tabela łącząca, gdzie kluczem jest para (id_wypozyczenia, id_filmu).

Klucz naturalny kontra sztuczny

Naturalny pochodzi z rzeczywistości: PESEL, numer ISBN, numer rejestracyjny. Sztuczny (surogatowy) nie znaczy nic poza bazą — to zwykle kolumna id INT AUTO_INCREMENT.

W praktyce projektowej wybiera się sztuczny i tego uzasadnienia oczekuje egzamin. Powody są trzy: jest krótki (4 bajty zamiast 11 znaków), nigdy się nie zmienia (numer telefonu klienta owszem — a zmiana klucza podstawowego pociąga zmiany we wszystkich tabelach podrzędnych) i istnieje zawsze, także dla klienta, który nie chce podać PESEL-u.

Integralność encji

Każdy wiersz musi być jednoznacznie identyfikowalny, czyli klucz podstawowy jest unikalny i różny od NULL. Bez tego nie da się wskazać konkretnego rekordu do aktualizacji ani usunięcia — DBMS nie wie, o który wiersz chodzi.

Integralność referencyjna

Wartość klucza obcego musi wskazywać na istniejący wiersz w tabeli nadrzędnej albo być równa NULL. Nie może istnieć wypożyczenie klienta numer 77, jeśli w tabeli klientów nie ma numeru 77. To dlatego DBMS blokuje usunięcie klienta, który ma wypożyczenia.

3

Relacje 1:1, 1:N i N:M

Typ związku rozpoznajesz metodą dwóch pytań zadanych w obie strony. „Ilu klientów może mieć jedno wypożyczenie?" — jednego. „Ile wypożyczeń może mieć jeden klient?" — wiele. Jeden i wiele, czyli 1:N. Odpowiedź „wiele" po obu stronach oznacza N:M i wymusza trzecią tabelę.

1:1 — jeden do jednego

Jednemu wierszowi odpowiada dokładnie jeden wiersz w drugiej tabeli. Rzadka, bo zwykle takie dane można trzymać w jednej tabeli. Stosuje się ją, gdy część kolumn jest rzadko używana albo poufna, np. pracownicy i dane_placowe.



Realizacja: klucz obcy w jednej z tabel, dodatkowo oznaczony jako UNIQUE.

1:N — jeden do wielu

Najczęstsza relacja w bazach. Jeden klient ma wiele wypożyczeń, jeden reżyser nakręcił wiele filmów, jedna kategoria obejmuje wiele produktów.



Realizacja: klucz obcy trafia zawsze po stronie „wiele". Kolumna id_klienta stoi w tabeli wypożyczeń, nie odwrotnie.

N:M — wiele do wielu

Jeden film ma wielu aktorów, jeden aktor gra w wielu filmach. Jeden uczeń ma wielu nauczycieli, jeden nauczyciel wielu uczniów.



Realizacja: w modelu relacyjnym nie da się jej zapisać wprost. Trzeba dołożyć tabelę łączącą (asocjacyjną, pośredniczącą), która rozbija N:M na dwie relacje 1:N.

Tabela łącząca — jak rozbić N:M

Tabela łącząca zawiera co najmniej dwa klucze obce — po jednym do każdej z łączonych tabel — i najczęściej to właśnie ich para tworzy złożony klucz podstawowy. Może też przechowywać atrybuty samego związku, np. rolę, jaką aktor zagrał w danym filmie:

CREATE TABLE obsada (
  id_filmu  INT NOT NULL,
  id_aktora INT NOT NULL,
  rola      VARCHAR(80),
  PRIMARY KEY (id_filmu, id_aktora),
  FOREIGN KEY (id_filmu)  REFERENCES filmy(id_filmu),
  FOREIGN KEY (id_aktora) REFERENCES aktorzy(id_aktora)
) ENGINE = InnoDB;

Klucz złożony (id_filmu, id_aktora) załatwia od razu dwie sprawy: identyfikuje wiersz i blokuje wpisanie tego samego aktora dwa razy do tego samego filmu. Jeśli jednak aktor ma zagrać w filmie dwie różne role, klucz trzeba poszerzyć o kolumnę rola albo wprowadzić klucz sztuczny — to typowe pytanie „uzasadnij wybór klucza".

4

Model ER i diagram związków encji

Model ER (Entity-Relationship, model związków encji) to szkic bazy robiony przed napisaniem pierwszego CREATE TABLE. Opisuje, co przechowujemy i jak to jest ze sobą powiązane — bez typów danych i bez SQL-a.

Elementy modelu
  • Encja — obiekt, o którym gromadzimy dane. W notacji Chena prostokąt. Po implementacji staje się tabelą. Encje nazywamy rzeczownikami: Klient, Film, Wypożyczenie.
  • Atrybut — cecha encji, w notacji Chena elipsa. Staje się kolumną. Atrybut będący kluczem podkreślamy.
  • Związek — powiązanie encji, w notacji Chena romb podpisany czasownikiem („wypożycza", „zawiera").
  • Liczebność (kardynalność) — informacja, ile wystąpień jednej encji odpowiada wystąpieniu drugiej: 1:1, 1:N, N:M.
  • Encja słaba — nie ma własnego pełnego klucza i istnieje wyłącznie w powiązaniu z encją nadrzędną (pozycja wypożyczenia bez wypożyczenia nie ma sensu).
Jak oznacza się liczebność
  • Notacja Chena — nad linią związku pisze się symbole 1, N, M przy odpowiednich końcach.
  • Notacja kurzej stopki (crow's foot) — najpopularniejsza w narzędziach typu MySQL Workbench. Rozwidlenie („kurza łapka") na końcu linii oznacza wiele, pojedyncza kreska oznacza jeden.
  • Opcjonalność — kółko przy końcu linii znaczy „zero lub…", czyli powiązanie nieobowiązkowe (klucz obcy dopuszcza NULL). Druga kreska znaczy „dokładnie jeden", czyli powiązanie obowiązkowe.
  • Zapis (min, max) — np. (0,N) czytamy tak: wystąpienie tej encji może nie mieć żadnego powiązania albo mieć ich wiele.
Docelowy schemat bazy wypożyczalni

Tak wygląda schemat, do którego dojdziemy w następnej sekcji drogą normalizacji. PK to klucz podstawowy, FK to klucz obcy:

  klienci                          rezyserzy
---------------------            ---------------------
PK id_klienta                    PK id_rezysera
   imie                             nazwisko
   nazwisko                         kraj
   telefon                                |
   email                                  | 1
      |                                   |
      | 1                                 | N
      |                             ---------------------
      | N                           filmy
---------------------               ---------------------
wypozyczenia                        PK id_filmu
---------------------                  tytul
PK id_wypozyczenia                     rok_produkcji
FK id_klienta                          cena_doba
   data_wypozyczenia                FK id_rezysera
   data_zwrotu                            |
   status                                 | 1
      |                                   |
      | 1                                 |
      |      pozycje_wypozyczenia         |
      |      -------------------------    |
      +-- N  PK,FK id_wypozyczenia        |
             PK,FK id_filmu          N ---+
                   liczba_dni

Zestawienie relacji:
  klienci      1 : N  wypozyczenia    -- FK id_klienta w wypozyczeniach
  rezyserzy    1 : N  filmy           -- FK id_rezysera w filmach
  wypozyczenia N : M  filmy           -- realizowane przez tabele laczaca
                                      -- pozycje_wypozyczenia: dwie relacje
                                      -- 1:N i klucz podstawowy zlozony
Kolejność pracy nad projektem
  1. Analiza wymagań — wypisz z treści zadania rzeczowniki (kandydaci na encje) i czasowniki (kandydaci na związki).
  2. Model konceptualny — diagram ER: encje, atrybuty, związki i ich liczebność. Bez typów danych.
  3. Model logiczny — zamiana encji na tabele, związków na klucze obce, rozbicie N:M tabelą łączącą, normalizacja do 3NF.
  4. Model fizyczny — dobór typów kolumn, więzów, indeksów i silnika (dla kluczy obcych w MySQL musi to być InnoDB, nie MyISAM).
  5. Implementacja — polecenia DDL, czyli CREATE DATABASE i CREATE TABLE. Pełną składnię znajdziesz w artykule o języku zapytań SQL.

5

Normalizacja: 1NF, 2NF, 3NF krok po kroku

Normalizacja to porządkowanie tabel tak, żeby każdy fakt był zapisany dokładnie raz. Postacie normalne są kumulatywne: żeby tabela była w 3NF, musi wcześniej spełniać 1NF i 2NF. Poniżej jeden przykład przeprowadzony przez wszystkie trzy etapy — dokładnie tak, jak w zadaniu „doprowadź poniższą tabelę do trzeciej postaci normalnej".

Definicje postaci normalnych
PostaćWarunekTypowe naruszenie
1NFKażda komórka zawiera jedną, niepodzielną wartość; brak powtarzających się grup kolumn; każdy wiersz jest unikalny.lista wartości w jednej komórce („Matrix, Incepcja") albo kolumny film_1, film_2, film_3
2NFJest w 1NF i każdy atrybut niekluczowy zależy od całego klucza podstawowego, a nie od jego części.zależność częściowa: przy kluczu (nr_wyp, id_filmu) kolumna tytul zależy tylko od id_filmu
3NFJest w 2NF i żaden atrybut niekluczowy nie zależy od innego atrybutu niekluczowego.zależność przechodnia: telefon zależy od id_klienta, a ten nie jest kluczem tej tabeli
BCNFZaostrzona 3NF: każdy wyznacznik zależności funkcyjnej musi być kluczem kandydującym.rzadko wymagana na egzaminie — warto znać nazwę i to, że jest silniejsza od 3NF

Zdanie-wytrych, którym sprawdzisz 3NF w pięć sekund: „każdy atrybut zależy od klucza, całego klucza i tylko od klucza".

Punkt wyjścia: jedna wielka tabela

Wypożyczalnia prowadzi ewidencję w arkuszu kalkulacyjnym. Jeden wiersz to jedno wypożyczenie:

WYPOZYCZENIA  (postac nieznormalizowana)

nr | klient         | telefon     | data       | wypozyczone filmy
---+----------------+-------------+------------+---------------------------
1 | Anna Nowak     | 601-100-200 | 2026-03-04 | Matrix (Wachowscy);
 |                |             |            | Incepcja (Nolan)
2 | Piotr Kowalski | 602-300-400 | 2026-03-04 | Incepcja (Nolan)
3 | Anna Nowak     | 601-100-200 | 2026-03-09 | Dunkierka (Nolan);
 |                |             |            | Matrix (Wachowscy)

Problemy widać gołym okiem. Nie da się zapytać „ile razy wypożyczono Matrixa", bo tytuły siedzą sklejone w jednej komórce. Telefon Anny jest w dwóch miejscach — po zmianie numeru trzeba poprawić oba wiersze, a jeśli poprawi się tylko jeden, baza zacznie sama sobie zaprzeczać.

Krok 1 — doprowadzenie do 1NF

Naruszona reguła: komórka wypozyczone filmy zawiera wiele wartości, czyli nie jest atomowa. Dodatkowo upchnięto w niej dwie różne informacje: tytuł i reżysera.



Co robimy: rozbijamy listę na osobne wiersze (jeden wiersz = jedna wypożyczona pozycja) i rozdzielamy tytuł od reżysera na osobne kolumny. Skoro numer wypożyczenia zaczyna się powtarzać, klucz podstawowy musi być złożony: (nr_wyp, id_filmu).

WYPOZYCZENIA_1NF        PK: (nr_wyp, id_filmu)

nr_wyp | id_filmu | tytul     | rezyser   | id_kl | klient         | telefon     | data
-------+----------+-----------+-----------+-------+----------------+-------------+-----------
 1   |    10    | Matrix    | Wachowscy |   5   | Anna Nowak     | 601-100-200 | 2026-03-04
 1   |    11    | Incepcja  | Nolan     |   5   | Anna Nowak     | 601-100-200 | 2026-03-04
 2   |    11    | Incepcja  | Nolan     |   8   | Piotr Kowalski | 602-300-400 | 2026-03-04
 3   |    12    | Dunkierka | Nolan     |   5   | Anna Nowak     | 601-100-200 | 2026-03-09
 3   |    10    | Matrix    | Wachowscy |   5   | Anna Nowak     | 601-100-200 | 2026-03-09

Tabela jest już w 1NF — da się na niej wykonać WHERE tytul = 'Matrix'. Ale redundancja urosła: dane Anny powtarzają się teraz w czterech wierszach. To normalne na tym etapie i właśnie dlatego 1NF to dopiero początek.

Krok 2 — doprowadzenie do 2NF

Naruszona reguła: klucz jest złożony, a atrybuty niekluczowe zależą tylko od jego części — to zależność częściowa.

  • tytul i rezyser zależą wyłącznie od id_filmu — znając numer filmu, znasz tytuł, bez patrzenia na numer wypożyczenia.
  • id_kl, klient, telefon i data zależą wyłącznie od nr_wyp.

Co robimy: wyprowadzamy każdą taką grupę do własnej tabeli, a w tabeli z kluczem złożonym zostawiamy tylko to, co naprawdę zależy od całej pary, czyli atrybuty samego związku.

FILMY   PK: id_filmu             WYPOZYCZENIA   PK: nr_wyp
-----------------------------    ------------------------------------------------------
id_filmu | tytul     | rezyser   nr_wyp | id_kl | klient         | telefon     | data
 10    | Matrix    | Wachowscy    1   |   5   | Anna Nowak     | 601-100-200 | 2026-03-04
 11    | Incepcja  | Nolan        2   |   8   | Piotr Kowalski | 602-300-400 | 2026-03-04
 12    | Dunkierka | Nolan        3   |   5   | Anna Nowak     | 601-100-200 | 2026-03-09

POZYCJE_WYPOZYCZENIA
PK: (nr_wyp, id_filmu)
FK: nr_wyp   -> WYPOZYCZENIA(nr_wyp)
FK: id_filmu -> FILMY(id_filmu)
-----------------------------
nr_wyp | id_filmu | liczba_dni
 1   |    10    |     3
 1   |    11    |     3
 2   |    11    |     7
 3   |    12    |     2
 3   |    10    |     2

Zwróć uwagę, że kolumna liczba_dni słusznie została w tabeli łączącej — zależy jednocześnie od wypożyczenia i od filmu, czyli od całego klucza.



Skrót do zapamiętania: jeśli klucz podstawowy jest jednokolumnowy, tabela w 1NF automatycznie spełnia 2NF — nie ma czego naruszyć, bo klucz nie ma części. Naruszenie 2NF występuje wyłącznie przy kluczu złożonym.

Krok 3 — doprowadzenie do 3NF

Naruszona reguła: zależność przechodnia — atrybut niekluczowy zależy od innego atrybutu niekluczowego.

  • W tabeli WYPOZYCZENIA zachodzi łańcuch nr_wyp → id_kl → klient, telefon. Nazwisko i telefon nie są cechą wypożyczenia, tylko klienta.
  • W tabeli FILMY nazwisko reżysera powtarza się przy każdym jego filmie. Gdybyśmy dołożyli kraj reżysera, powielałby się razem z nazwiskiem — to ten sam mechanizm.

Co robimy: wydzielamy tabele KLIENCI i REZYSERZY, a w tabelach pierwotnych zostawiamy same klucze obce.

KLIENCI   PK: id_klienta                REZYSERZY   PK: id_rezysera
------------------------------------    ---------------------------------------
id_klienta | imie  | nazwisko | telefon  id_rezysera | nazwisko  | kraj
   5     | Anna  | Nowak    | 601-100-200    1     | Wachowscy | USA
   8     | Piotr | Kowalski | 602-300-400    2     | Nolan     | Wielka Brytania

FILMY   PK: id_filmu, FK: id_rezysera    WYPOZYCZENIA  PK: nr_wyp, FK: id_klienta
-------------------------------------    ---------------------------------------------
id_filmu | tytul     | rok  | id_rez     nr_wyp | id_klienta | data_wyp   | data_zwrotu
 10    | Matrix    | 1999 |   1           1   |     5      | 2026-03-04 | 2026-03-07
 11    | Incepcja  | 2010 |   2           2   |     8      | 2026-03-04 | NULL
 12    | Dunkierka | 2017 |   2           3   |     5      | 2026-03-09 | NULL

POZYCJE_WYPOZYCZENIA   PK: (nr_wyp, id_filmu)   -- bez zmian od kroku 2

Baza jest w 3NF. Numer telefonu Anny występuje teraz w jednym miejscu, nazwisko reżysera również. Każdy fakt zapisany dokładnie raz — to jest cel całej operacji, a nie liczba tabel sama w sobie.

Jak odpowiadać na pytanie „w której postaci normalnej jest ta tabela?"

Sprawdzaj po kolei i zatrzymaj się na pierwszym „nie":

  1. Czy w którejkolwiek komórce jest lista wartości albo czy występują kolumny typu telefon1, telefon2? Jeśli tak — tabela nie jest nawet w 1NF.
  2. Czy klucz podstawowy jest złożony? Jeśli nie — 2NF spełniona z automatu. Jeśli tak — sprawdź, czy każda kolumna niekluczowa potrzebuje obu części klucza. Jeśli któraś nie potrzebuje — tabela jest tylko w 1NF.
  3. Czy jakaś kolumna niekluczowa daje się wyliczyć z innej niekluczowej (nazwisko z numeru klienta, nazwa miasta z kodu pocztowego, wartość pozycji z ceny i ilości)? Jeśli tak — tabela jest tylko w 2NF.
  4. Jeśli wszystkie trzy sprawdzenia wypadły pomyślnie — 3NF.

6

Anomalie i denormalizacja

Anomalie to konkretne szkody, jakie wyrządza nieznormalizowana tabela. Egzamin pyta o nie wprost: „wymień anomalię, która wystąpi po usunięciu wiersza". Wszystkie trzy pokazujemy na tabeli sprzed normalizacji.

Anomalia wstawiania

Nie da się dodać danych, bo brakuje niepowiązanej z nimi informacji. Do jednej wielkiej tabeli nie wpiszesz nowego filmu, którego nikt jeszcze nie wypożyczył — musiałbyś zostawić puste numer wypożyczenia i dane klienta, a numer wypożyczenia jest częścią klucza. Tak samo nie zarejestrujesz klienta, który jeszcze nic nie wypożyczył.

Anomalia aktualizacji

Ta sama informacja leży w wielu wierszach, więc zmiana wymaga poprawienia ich wszystkich naraz. Anna zmienia numer telefonu — trzeba zaktualizować cztery wiersze. Jeśli poprawisz trzy, baza zawiera dwa sprzeczne numery tej samej osoby i przestaje być spójna.

Anomalia usuwania

Usuwając jedną informację, tracisz przy okazji inną. Kasujesz wypożyczenie numer 2 — i razem z nim znika jedyny ślad po Piotrze Kowalskim. Baza traci dane, których nikt nie kazał usuwać.

Denormalizacja — świadome cofnięcie się o krok

Normalizacja ma swoją cenę: dane rozproszone po kilku tabelach trzeba przy odczycie sklejać złączeniami, a każdy JOIN kosztuje czas. Denormalizacja to celowe wprowadzenie kontrolowanej redundancji, żeby przyspieszyć odczyt.

  • Kiedy ma sens: baza jest czytana wielokrotnie częściej, niż zapisywana (raporty, hurtownie danych, statystyki), a zapytanie wymaga złączenia wielu dużych tabel.
  • Przypadek w pełni uzasadniony: na fakturze zapisuje się cenę produktu z dnia sprzedaży. To wygląda na redundancję, ale nią nie jest — cena w cenniku się zmienia, a kwota na wystawionym dokumencie musi zostać taka, jaka była. To po prostu dwa różne fakty.
  • Inny typowy przykład: kolumna liczba_wypozyczen w tabeli filmów zamiast liczenia jej za każdym razem funkcją COUNT.
  • Cena: wracają anomalie aktualizacji. Nadmiarowe dane trzeba synchronizować — wyzwalaczem albo w kodzie aplikacji. Bez tego licznik zacznie kłamać.
  • Zasada na egzamin: najpierw normalizujesz do 3NF, dopiero potem, mając zmierzony problem z wydajnością, rozważasz odstępstwo. Denormalizacja „na wszelki wypadek" to po prostu zły projekt.

7

Typy danych MySQL i więzy integralności

Zadanie praktyczne prawie zawsze zawiera polecenie „utwórz tabelę o podanej strukturze, dobierając odpowiednie typy danych". Punktowany jest sensowny dobór, a nie wpisanie wszędzie VARCHAR(255).

Najważniejsze typy w MySQL
TypRozmiar / zakresKiedy używać
INT4 bajty; od −2 147 483 648 do 2 147 483 647, a z UNSIGNED od 0 do 4 294 967 295klucze podstawowe i obce, liczniki, identyfikatory
TINYINT1 bajt; od −128 do 127 (UNSIGNED od 0 do 255)małe liczniki, oceny, wiek, liczba dni wypożyczenia
VARCHAR(n)tekst o zmiennej długości; limit wiersza to 65 535 bajtów. Zajmuje tyle, ile realnie wpisano, plus 1–2 bajty na długośćnazwiska, tytuły, adresy e-mail — teksty o różnej długości
CHAR(n)stała długość od 0 do 255 znaków, krótsze wartości dopełniane spacjamidane o niezmiennej długości: PESEL (11 znaków), kod pocztowy, symbol waluty
TEXTdo 65 535 bajtów, przechowywany poza wierszem; nie da się nadać mu wartości DEFAULTopisy, streszczenia filmów, treść komentarza
DATE3 bajty, format RRRR-MM-DD, zakres od 1000-01-01 do 9999-12-31data urodzenia, data wypożyczenia — gdy godzina nie ma znaczenia
DATETIMEformat RRRR-MM-DD GG:MM:SS, ten sam zakres lat co DATEmoment zdarzenia: logowanie, rejestracja zamówienia
TIMESTAMP4 bajty, zakres od 1970 do 2038 roku, uwzględnia strefę czasowąznacznik utworzenia lub ostatniej modyfikacji rekordu
DECIMAL(p,s)liczba dokładna: p cyfr znaczących, z czego s po przecinku. DECIMAL(8,2) to wartości do 999 999,99pieniądze — ceny, kwoty, salda
FLOAT / DOUBLE4 / 8 bajtów, zapis przybliżony (zmiennoprzecinkowy)wielkości fizyczne: temperatura, masa, współrzędne. Nigdy do pieniędzy
BOOLEANw MySQL to alias typu TINYINT(1): 0 to fałsz, każda inna wartość to prawdaflagi typu „czy zwrócony", „czy konto aktywne"
ENUMjedna wartość z zamkniętej listy podanej przy tworzeniu kolumny; wewnętrznie zapisywana jako liczbastatus wypożyczenia: 'aktywne', 'zwrocone', 'zalegle'
Zasada doboru typu — cztery pytania

  1. Czy to liczba, na której wykonuje się działania? Numer telefonu i PESEL to nie liczby — mają wiodące zera i myślniki, a nikt ich nie dodaje. Idą jako VARCHAR albo CHAR.
  2. Czy to pieniądze? Zawsze DECIMAL. FLOAT zapisuje wartości w przybliżeniu i po zsumowaniu setek pozycji rachunek przestaje się zgadzać o grosze.
  3. Czy długość tekstu jest stała? Stała — CHAR, zmienna — VARCHAR, bardzo długa i nieprzeszukiwana — TEXT.
  4. Czy zbiór wartości jest zamknięty i krótki? Wtedy ENUM albo osobna tabela słownikowa z kluczem obcym. Tabela słownikowa jest elastyczniejsza — dodanie nowej wartości do ENUM wymaga przebudowy struktury tabeli.
Więzy integralności (ograniczenia)
  • NOT NULL — kolumna nie może pozostać pusta. Pilnuje, żeby wypożyczenie zawsze miało klienta i datę.
  • UNIQUE — wartości w kolumnie nie mogą się powtarzać, ale może wystąpić NULL (w MySQL nawet wielokrotnie). To główna różnica wobec klucza podstawowego, obok tego, że więzów UNIQUE może być w tabeli wiele.
  • DEFAULT — wartość wpisywana, gdy polecenie INSERT pominie kolumnę, np. status DEFAULT 'aktywne'.
  • CHECK — warunek, który musi być spełniony, np. CHECK (cena_doba >= 0). W MySQL jest egzekwowany od wersji 8.0.16; we wcześniejszych składnia była przyjmowana, ale ignorowana.
  • AUTO_INCREMENT — automatyczne nadawanie kolejnego numeru. Tylko dla kolumny liczbowej będącej kluczem lub indeksem. Usunięcie rekordu nie zwalnia numeru: po skasowaniu wiersza 5 następny dostanie 6, w numeracji zostanie dziura i jest to zachowanie prawidłowe.
  • PRIMARY KEY — połączenie UNIQUE i NOT NULL, jedno na tabelę.
  • FOREIGN KEY — więz referencyjny między tabelami. W MySQL wymaga silnika InnoDB oraz zgodnych typów kolumn po obu stronach.
Implementacja schematu z sekcji 4
CREATE DATABASE wypozyczalnia
  DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;

USE wypozyczalnia;

CREATE TABLE klienci (
  id_klienta INT AUTO_INCREMENT PRIMARY KEY,
  imie       VARCHAR(30)  NOT NULL,
  nazwisko   VARCHAR(50)  NOT NULL,
  telefon    VARCHAR(15),
  email      VARCHAR(100) UNIQUE,
  data_rej   DATE         NOT NULL
) ENGINE = InnoDB;

CREATE TABLE rezyserzy (
  id_rezysera INT AUTO_INCREMENT PRIMARY KEY,
  nazwisko    VARCHAR(60) NOT NULL,
  kraj        VARCHAR(40)
) ENGINE = InnoDB;

CREATE TABLE filmy (
  id_filmu      INT AUTO_INCREMENT PRIMARY KEY,
  tytul         VARCHAR(120) NOT NULL,
  rok_produkcji SMALLINT,
  cena_doba     DECIMAL(6,2) NOT NULL DEFAULT 5.00,
  opis          TEXT,
  id_rezysera   INT,
  CHECK (cena_doba >= 0),
  FOREIGN KEY (id_rezysera) REFERENCES rezyserzy(id_rezysera)
      ON DELETE SET NULL
      ON UPDATE CASCADE
) ENGINE = InnoDB;

CREATE TABLE wypozyczenia (
  id_wypozyczenia   INT AUTO_INCREMENT PRIMARY KEY,
  id_klienta        INT  NOT NULL,
  data_wypozyczenia DATE NOT NULL,
  data_zwrotu       DATE,
  status ENUM('aktywne','zwrocone','zalegle') NOT NULL DEFAULT 'aktywne',
  FOREIGN KEY (id_klienta) REFERENCES klienci(id_klienta)
      ON DELETE RESTRICT
      ON UPDATE CASCADE
) ENGINE = InnoDB;

CREATE TABLE pozycje_wypozyczenia (
  id_wypozyczenia INT NOT NULL,
  id_filmu        INT NOT NULL,
  liczba_dni      TINYINT UNSIGNED NOT NULL DEFAULT 3,
  PRIMARY KEY (id_wypozyczenia, id_filmu),
  FOREIGN KEY (id_wypozyczenia) REFERENCES wypozyczenia(id_wypozyczenia)
      ON DELETE CASCADE,
  FOREIGN KEY (id_filmu) REFERENCES filmy(id_filmu)
      ON DELETE RESTRICT
) ENGINE = InnoDB;

Kolejność tworzenia tabel nie jest dowolna: tabela z kluczem obcym musi powstać po tabeli, na którą wskazuje. Przy usuwaniu kolejność jest odwrotna — najpierw tabele podrzędne. To najczęstsza przyczyna błędu przy odtwarzaniu bazy ze zrzutu.

8

Indeksy, akcje referencyjne i transakcje

Trzy tematy, które kończą projekt bazy: przyspieszenie odczytu, decyzja o losie rekordów podrzędnych i zabezpieczenie operacji, które muszą się wykonać w całości albo wcale.

Indeksy

Indeks to dodatkowa struktura (najczęściej B-drzewo) trzymająca posortowane wartości kolumny wraz ze wskaźnikami do wierszy. Działa jak skorowidz w książce: zamiast przeglądać wszystkie strony, sprawdzasz w spisie i idziesz prosto pod właściwy numer. Bez indeksu DBMS wykonuje pełne przeglądanie tabeli (full table scan) — przy milionie wierszy sprawdza milion wierszy.

Indeks pomaga, gdy…
  • kolumna często pojawia się w WHERE, JOIN lub ORDER BY;
  • ma dużo różnych wartości, czyli wysoką selektywność: nazwisko, e-mail, numer;
  • tabela jest duża i głównie czytana.
Indeks szkodzi, gdy…
  • tabela jest intensywnie zapisywana — każdy INSERT, UPDATE i DELETE musi zaktualizować także indeksy;
  • kolumna ma mało różnych wartości (płeć, status z trzema opcjami) — optymalizator i tak go pominie;
  • indeksów jest zbyt wiele — rosną rozmiar bazy i czas zapisu, a zysk przy odczycie żaden.

Pamiętaj: PRIMARY KEY i UNIQUE tworzą indeks automatycznie — nie trzeba go zakładać osobno. Ręcznie zakłada się go poleceniem CREATE INDEX idx_nazwisko ON klienci(nazwisko);

Akcje referencyjne ON DELETE i ON UPDATE

Określają, co DBMS zrobi z wierszami podrzędnymi, gdy skasujesz lub zmienisz wiersz nadrzędny. Te same słowa kluczowe działają w obu klauzulach:

AkcjaSkutek przy usunięciu rekordu nadrzędnegoKiedy stosować
CASCADEUsuwa także wszystkie powiązane rekordy podrzędne. Kasujesz wypożyczenie — znikają jego pozycje.gdy rekord podrzędny nie ma sensu bez nadrzędnego (encja słaba)
SET NULLWpisuje NULL do kolumny klucza obcego. Usuwasz reżysera — filmy zostają, tylko bez przypisanego reżysera.gdy powiązanie jest opcjonalne; kolumna FK nie może mieć NOT NULL
RESTRICTBlokuje operację i zgłasza błąd, dopóki istnieją rekordy podrzędne. Nie usuniesz klienta, który ma wypożyczenia.zachowanie domyślne i najbezpieczniejsze; chroni przed przypadkową utratą danych
NO ACTIONW MySQL na silniku InnoDB zachowuje się tak samo jak RESTRICT.zapis zgodny z brzmieniem standardu SQL
SET DEFAULTMa wpisać wartość domyślną, ale silnik InnoDB tej akcji nie realizuje.wymieniaj przy wyliczaniu akcji, nie stosuj w MySQL

Częsty duet w praktyce: ON DELETE RESTRICT ON UPDATE CASCADE — nie pozwól skasować rekordu nadrzędnego, ale jeśli zmieni się jego identyfikator, przepisz zmianę do rekordów podrzędnych automatycznie.

Transakcje i zasada ACID

Transakcja to ciąg operacji traktowany jako jedna niepodzielna całość. Klasyczny przykład to przelew: odjęcie kwoty z jednego konta i dopisanie jej do drugiego. Gdyby po pierwszej operacji zabrakło prądu, pieniądze by wyparowały. Transakcja temu zapobiega.

START TRANSACTION;
  INSERT INTO wypozyczenia (id_klienta, data_wypozyczenia)
  VALUES (5, '2026-03-04');

  INSERT INTO pozycje_wypozyczenia (id_wypozyczenia, id_filmu, liczba_dni)
  VALUES (LAST_INSERT_ID(), 10, 3);
COMMIT;      -- zatwierdza obie zmiany na trwale
-- ROLLBACK; -- wycofalby obie, gdyby druga operacja sie nie powiodla
  • A — Atomicity (atomowość): wykonuje się całość albo nic. Nie ma stanu „w połowie".
  • C — Consistency (spójność): baza przed transakcją i po niej spełnia wszystkie więzy integralności.
  • I — Isolation (izolacja): równolegle wykonywane transakcje nie widzą swoich niezatwierdzonych zmian i nie psują sobie wyników.
  • D — Durability (trwałość): po COMMIT dane przetrwają awarię zasilania — są już zapisane na nośniku, a nie tylko w pamięci.

W MySQL transakcje i klucze obce obsługuje silnik InnoDB. Starszy MyISAM nie obsługuje ani jednego, ani drugiego — to typowe pytanie „który silnik wybierzesz i dlaczego".

Najczęstsze pułapki egzaminacyjne
  • Klucz obcy po złej stronie relacji 1:N. Zawsze trafia do tabeli po stronie „wiele". Kolumna id_klienta stoi w wypożyczeniach, a nie kolumna id_wypozyczenia w klientach — inaczej klient mógłby mieć tylko jedno wypożyczenie.
  • Zapisanie relacji N:M w dwóch tabelach. Model relacyjny tego nie obsługuje. Zawsze trzy tabele: dwie główne i jedna łącząca z dwoma kluczami obcymi.
  • Mylenie UNIQUE z PRIMARY KEY. UNIQUE dopuszcza NULL i może wystąpić w tabeli wielokrotnie. PRIMARY KEY jest jeden i nie dopuszcza NULL.
  • Twierdzenie, że tabela z kluczem jednokolumnowym narusza 2NF. Nie może — naruszenie 2NF wymaga klucza złożonego. Przy kluczu pojedynczym przechodzisz od razu do sprawdzania 3NF.
  • Zatrzymanie normalizacji na rozbiciu komórki. Rozbicie listy na wiersze to dopiero 1NF. Zadanie zwykle każe dojść do 3NF, więc trzeba jeszcze wydzielić tabele słownikowe.
  • FLOAT do kwot pieniężnych. Zapis przybliżony, sumy przestają się zgadzać. Ceny i salda zawsze w DECIMAL.
  • Numer telefonu i PESEL jako INT. Giną wiodące zera, a myślników i tak nie da się zapisać. To ciągi znaków, nie liczby.
  • ON DELETE SET NULL przy kolumnie NOT NULL. Sprzeczność — baza nie przyjmie takiej definicji. SET NULL wymaga kolumny dopuszczającej NULL.
  • Klucze obce na silniku MyISAM. Zostaną przyjęte składniowo, ale nie będą pilnowane. Do bazy z więzami wybierasz InnoDB.
Sprawdź, czy odróżnisz 2NF od 3NF

Normalizacja wchodzi do głowy dopiero wtedy, gdy przerobisz kilka tabel samodzielnie i przestaniesz się zastanawiać, po której stronie postawić klucz obcy. Rozwiąż test z tego działu i zobacz, które reguły jeszcze uciekają.