Tenzan Academy
Motyw
  • 8 sekcji
  • 24 pytania
  • 22 fiszki

SQL i bazy danych

Fundamenty SQL dla inżynierów: JOIN-y, indeksy, transakcje ACID, poziomy izolacji, normalizacja, N+1, SQL injection oraz wybór SQL czy NoSQL.

SQL i bazy danych

Fundamenty SQL dla inżynierów: JOIN-y, indeksy, transakcje ACID, poziomy izolacji, normalizacja, N+1, SQL injection oraz wybór SQL czy NoSQL.

Quiz

JOIN-y: kiedy który jest poprawny

na egzaminie

JOIN łączy wiersze z wielu tabel na podstawie warunku dopasowania kluczy. Zły typ JOIN nie jest błędem składniowym. Silnik wykona zapytanie, ale wynik będzie cicho niepoprawny: zgubione wiersze albo utrata danych, bez komunikatu błędu.

Termin kluczowy

INNER JOIN: Zwraca tylko wiersze, dla których warunek dopasowania jest spełniony w obu tabelach. Klient bez zamówień znika z wyniku całkowicie.

Termin kluczowy

LEFT JOIN (LEFT OUTER JOIN): Zwraca wszystkie wiersze z lewej tabeli, a z prawej dopasowane wartości lub NULL, gdy dopasowania brak. Domyślny wybór, gdy trzeba zachować 'właściciela' relacji nawet bez powiązanych rekordów.

Termin kluczowy

RIGHT JOIN (RIGHT OUTER JOIN): Lustrzane odbicie LEFT JOIN: wszystkie wiersze z prawej tabeli, dopasowanie z lewej lub NULL. W praktyce rzadko używany, bo ten sam wynik da się uzyskać zamieniając kolejność tabel i pisząc LEFT JOIN, co jest czytelniejsze.

Termin kluczowy

FULL OUTER JOIN: Suma LEFT i RIGHT JOIN: wszystkie wiersze z obu tabel, NULL tam, gdzie dopasowania brak po którejkolwiek stronie. MySQL nie wspiera tej składni natywnie, trzeba ją emulować przez UNION dwóch LEFT JOIN.

Termin kluczowy

CROSS JOIN: Iloczyn kartezjański: każdy wiersz lewej tabeli z każdym wierszem prawej, bez warunku dopasowania. Liczba wynikowych wierszy to iloczyn liczności obu tabel, przy dużych tabelach to szybki sposób na wyczerpanie pamięci.

Termin kluczowy

SELF JOIN: Złączenie tabeli samej ze sobą przez dwa aliasy, typowe dla struktur hierarchicznych, np. pracownik i jego przełożony w tej samej tabeli employees.

LEFT JOIN zachowujący klientów bez zamówień sql
-- Klienci wraz z liczbą zamówień, łącznie z klientami bez żadnego zamówienia
SELECT c.id, c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY order_count DESC;
Pułapka

Filtrowanie kolumny z dołączonej tabeli w WHERE zamiast w ON cicho zamienia LEFT JOIN w INNER JOIN. WHERE o.status = 'paid' odrzuca wiersze z o.status IS NULL, więc klienci bez zamówień i tak znikają. Warunek dopasowania musi być w ON.

Typ JOINWiersze z lewej bez dopasowaniaWiersze z prawej bez dopasowaniaTypowe zastosowanie
INNER JOINodrzuconeodrzuconełączenie dopasowanych rekordów obu stron, np. zamówienie z istniejącym produktem
LEFT JOINzachowane (NULL po prawej)odrzuconelista głównej encji niezależnie od istnienia powiązań, np. wszyscy klienci z liczbą zamówień
RIGHT JOINodrzuconezachowane (NULL po lewej)rzadko używany, zwykle zastępowany zamianą tabel i LEFT JOIN
FULL OUTER JOINzachowane (NULL po prawej)zachowane (NULL po lewej)pełne zestawienie rozbieżności między dwoma zbiorami, np. audyt synchronizacji dwóch systemów
CROSS JOINn/d (iloczyn kartezjański)n/d (iloczyn kartezjański)generowanie kombinacji, np. każda data z każdym produktem w raporcie
Mnemonik

LEFT JOIN zachowuje LEWĄ stronę w całości, jak mówi nazwa. Gdy pytanie brzmi 'pokaż wszystkich klientów, także tych bez zamówień', odpowiedź to zawsze LEFT JOIN z tabeli klientów.

  • Potrzebujesz tylko dopasowanych par obu stron: INNER JOIN.
  • Musisz zachować wszystkie rekordy 'głównej' encji, nawet bez powiązań: LEFT JOIN z tej encji jako lewej tabeli.
  • Potrzebujesz pełnego zestawienia rozbieżności między dwoma zbiorami: FULL OUTER JOIN.
  • Generujesz kombinacje, np. kalendarz dat razy lista produktów: CROSS JOIN.
  • Porównujesz wiersze tej samej tabeli między sobą: SELF JOIN.
Dalej: 02 Indeksy: przyspieszenie odczytu kontra koszt zapisu

Indeksy: przyspieszenie odczytu kontra koszt zapisu

na egzaminie

Indeks to dodatkowa struktura danych, najczęściej B-drzewo, utrzymywana obok tabeli, która pozwala silnikowi znaleźć wiersze bez przeglądania całej tabeli. Ten sam mechanizm, który przyspiesza SELECT-y, dokłada pracę do każdego INSERT, UPDATE i DELETE, bo indeks trzeba aktualizować razem z danymi.

B-drzewo

Domyślna struktura indeksu w PostgreSQL, MySQL/InnoDB i SQL Server. Wyszukiwanie, wstawianie i usuwanie mają złożoność O(log n) względem liczby wierszy, dzięki czemu tabela z milionami rekordów jest przeszukiwana w kilkunastu porównaniach zamiast pełnego skanu.

Termin kluczowy

indeks pokrywający (covering index): Indeks zawierający wszystkie kolumny potrzebne do wykonania zapytania. Silnik w ogóle nie sięga wtedy do tabeli głównej (Index Only Scan w PostgreSQL, 'Using index' w EXPLAIN MySQL).

Termin kluczowy

indeks złożony i reguła lewego prefiksu: Indeks na (a, b, c) przyspiesza zapytania filtrujące po a, po (a, b) i po (a, b, c). Nie przyspiesza zapytania filtrującego tylko po b lub c, bo B-drzewo jest posortowane od lewej kolumny.

Dodanie indeksu i weryfikacja planu wykonania sql
CREATE INDEX idx_orders_customer_id ON orders (customer_id);

EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 42;

-- Przed indeksem:
-- Seq Scan on orders (cost=0.00..18334.00 rows=12 width=72) (actual time=45.231..89.442 rows=12 loops=1)
-- Planning Time: 0.112 ms
-- Execution Time: 89.478 ms

-- Po indeksie:
-- Index Scan using idx_orders_customer_id on orders (cost=0.43..8.45 rows=12 width=72) (actual time=0.028..0.041 rows=12 loops=1)
-- Planning Time: 0.098 ms
-- Execution Time: 0.067 ms
Pułapka

Indeks na kolumnie o niskiej kardynalności (np. flaga boolean is_active) rzadko pomaga. Planner szacuje, że dopasowanych wierszy jest zbyt dużo względem całej tabeli. Dlatego i tak wybierze Seq Scan, bo odczyt przez indeks byłby wolniejszy od skanu sekwencyjnego.

Pułapka

LIKE '%fraza%' z wiodącym znakiem wieloznacznym nie może wykorzystać zwykłego indeksu B-drzewa. Sortowanie po prefiksie nic nie daje, gdy szukana fraza jest w środku stringa. Potrzebny jest indeks trigramowy (pg_trgm) albo pełnotekstowy.

Dlaczego zapisy zwalniają

Każdy INSERT, UPDATE modyfikujący indeksowaną kolumnę i DELETE wymaga zaktualizowania nie tylko wiersza w tabeli, ale też wpisu w każdym indeksie na tej tabeli. Dziesięć indeksów na tabeli oznacza dziesięć dodatkowych operacji zapisu przy każdej zmianie danych.

  • Dodawaj indeks na kolumnach używanych w JOIN (klucze obce) i często filtrowanych w WHERE.
  • Sprawdź selektywność: indeks pomaga, gdy zawęża wynik do niewielkiego ułamka tabeli.
  • Unikaj nadmiarowych indeksów na tabelach z bardzo wysokim ruchem zapisu, każdy kolejny indeks to dodatkowy koszt na każdy zapis.
  • Kolumny w ORDER BY i GROUP BY też są dobrymi kandydatami, indeks może wyeliminować osobne sortowanie.
AspektBez indeksuZ indeksem
Czas odczytu przy filtrze WHERESkan całej tabeli (Seq Scan), rośnie liniowo z rozmiarem tabeliSkan przez B-drzewo (Index Scan), rośnie logarytmicznie
Czas zapisu (INSERT/UPDATE/DELETE)Tylko aktualizacja wiersza w tabeliDodatkowa aktualizacja wpisu w każdym powiązanym indeksie
Zajętość dyskuTylko dane tabeliDane tabeli plus struktura indeksu, zwykle kilkanaście do kilkudziesięciu procent dodatkowo
Decyzja planneraZawsze Seq ScanIndex Scan przy wysokiej selektywności, Seq Scan przy niskiej mimo istnienia indeksu
  1. Sprawdź plan zapytania przez EXPLAIN ANALYZE Zobacz, czy planner faktycznie wybiera Index Scan zamiast Seq Scan.
  2. Zmierz selektywność kolumny Ile unikalnych wartości względem liczby wierszy w tabeli.
  3. Potwierdź obecność kolumny w WHERE, JOIN lub ORDER BY Indeks pomaga tylko tam, gdzie jest faktycznie używany do filtrowania lub sortowania.
  4. Przetestuj na środowisku testowym Porównaj czas wykonania zapytania przed i po dodaniu indeksu na realistycznym wolumenie danych.
  5. Zmierz wpływ na zapisy Sprawdź czas INSERT/UPDATE na tabeli o dużym ruchu zapisu przed i po dodaniu indeksu.
Dalej: 03 Transakcje i właściwości ACID

Transakcje i właściwości ACID

na egzaminie

Transakcja grupuje wiele operacji w jedną niepodzielną całość: albo wszystkie się wykonają, albo żadna. ACID to cztery gwarancje, które silnik bazy danych musi utrzymać, żeby ta obietnica miała sens także przy awarii zasilania czy współbieżnym dostępie wielu klientów.

Termin kluczowy

Atomicity (atomowość): Wszystkie operacje w transakcji wykonują się w całości albo nie wykonują się wcale. Jeśli drugi z trzech UPDATE-ów w transakcji zawiedzie, pierwszy musi zostać wycofany, nie może zostać zapisany częściowy stan.

Termin kluczowy

Consistency (spójność): Transakcja przenosi bazę z jednego poprawnego stanu w drugi poprawny stan, respektując wszystkie ograniczenia: klucze obce, unikalność, CHECK. Baza nigdy nie zostaje zapisana w stanie łamiącym te reguły.

Termin kluczowy

Isolation (izolacja): Współbieżne transakcje nie widzą swoich niedokończonych zmian w sposób, który psułby poprawność wyniku. Stopień tej izolacji jest regulowany przez poziom izolacji i jest kompromisem między poprawnością a przepustowością.

Termin kluczowy

Durability (trwałość): Po zatwierdzeniu (COMMIT) zmiana przetrwa awarię zasilania czy restart procesu. Mechanizmem realizującym tę gwarancję jest write-ahead log (WAL): zmiana trafia najpierw na dysk do dziennika, zanim transakcja zostanie potwierdzona klientowi.

Atomowy przelew między dwoma kontami sql
BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;
Pułapka

Seria niezależnych zapytań bez jawnej transakcji jest tak bezpieczna, jak najsłabsze ogniwo. Awaria połączenia po pierwszym UPDATE, a przed drugim, zostawia bazę w niespójnym stanie. Gwarancje ACID tego nie naprawią, bo zapytania nie były jedną transakcją.

Write-Ahead Log

PostgreSQL, MySQL/InnoDB i większość silników relacyjnych zapisują zmianę najpierw sekwencyjnie do dziennika WAL, a później do stron danych. Zapis sekwencyjny do jednego pliku jest szybszy niż losowy zapis po całej tabeli. Po awarii dziennik pozwala odtworzyć zmiany zatwierdzone i niezatwierdzone.

  • Operacje modyfikujące więcej niż jedną tabelę, które muszą się powieść razem.
  • Odczyt-modyfikacja-zapis tej samej wartości, np. stan magazynowy.
  • Import wsadowy, gdzie częściowy import jest gorszy niż brak importu.
  • Operacje wymagające blokady wiersza do końca logiki biznesowej (SELECT ... FOR UPDATE).
Mnemonik

ACID: Atomicity, wszystko albo nic. Consistency, reguły bazy zawsze spełnione. Isolation, transakcje nie depczą sobie po nogach. Durability, po COMMIT zmiana przeżyje nawet wyłączenie prądu.

Dalej: 04 Poziomy izolacji i race condition, które kontrolują

Poziomy izolacji i race condition, które kontrolują

na egzaminie

Pełna izolacja (Serializable) daje absolutną poprawność, ale kosztuje przepustowość: więcej blokad, więcej oczekiwania, więcej odrzuconych transakcji do ponowienia. Standard SQL definiuje cztery poziomy izolacji, z których każdy świadomie dopuszcza pewne zjawiska współbieżności w zamian za wydajność.

Termin kluczowy

dirty read (odczyt brudny): Transakcja czyta dane zapisane przez inną transakcję, która jeszcze nie wykonała COMMIT i może się wycofać (ROLLBACK). Odczytana wartość mogła nigdy realnie nie istnieć.

Termin kluczowy

non-repeatable read (odczyt niepowtarzalny): Ta sama transakcja czyta ten sam wiersz dwukrotnie i dostaje różne wartości, bo inna transakcja zmieniła go i zatwierdziła pomiędzy odczytami.

Termin kluczowy

phantom read (odczyt widmo): Ta sama transakcja wykonuje dwukrotnie ten sam warunek WHERE. Za drugim razem widzi nowe wiersze albo brak wcześniej widzianych, bo inna transakcja wstawiła lub usunęła pasujące rekordy.

Poziom izolacjiDirty readNon-repeatable readPhantom read
Read Uncommittedmożliwymożliwymożliwy
Read Committedzablokowanymożliwymożliwy
Repeatable Readzablokowanyzablokowanymożliwy wg standardu SQL
Serializablezablokowanyzablokowanyzablokowany
Domyślne poziomy w praktyce

PostgreSQL i Oracle domyślnie startują na Read Committed. MySQL/InnoDB domyślnie na Repeatable Read i dodatkowo blokuje phantom read przy odczytach blokujących (SELECT ... FOR UPDATE) dzięki next-key lockom, co jest silniejsze niż wymaga sam standard SQL dla tego poziomu.

Lost update: dwie transakcje nadpisują ten sam wiersz bez blokady text
Transakcja A                              Transakcja B
BEGIN;                                    BEGIN;
SELECT balance FROM accounts WHERE id=1;  SELECT balance FROM accounts WHERE id=1;
-- odczytano 500                          -- odczytano 500
-- lokalnie: 500 - 100 = 400              -- lokalnie: 500 - 50 = 450
UPDATE accounts SET balance = 400
  WHERE id = 1;
COMMIT;                                   UPDATE accounts SET balance = 450
                                             WHERE id = 1;
                                           COMMIT;
-- wynik końcowy: 450, zmiana transakcji A zgubiona (lost update)
Naprawa przez blokadę wiersza (SELECT ... FOR UPDATE) sql
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- blokuje wiersz do końca transakcji
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
Pułapka

'Repeatable Read' w MySQL i w standardzie SQL to nie to samo. Standard dopuszcza phantom read, a InnoDB w praktyce blokuje go w większości przypadków. Zakładanie identycznego zachowania silników bez sprawdzenia dokumentacji to częste źródło buga widocznego dopiero pod obciążeniem.

  • Odczyty raportowe bez modyfikacji równoległych: Read Committed wystarcza w większości przypadków.
  • Logika odczytaj-zmodyfikuj-zapisz na tym samym wierszu: SELECT FOR UPDATE albo blokada optymistyczna z kolumną wersji.
  • Wielokrokowa logika biznesowa wymagająca spójnego 'zdjęcia' całej bazy przez cały czas transakcji: Repeatable Read lub Serializable.
  • Wysoka konkurencja o te same wiersze: unikaj Serializable, rosnąca liczba odrzuceń transakcji wymaga kosztownych retry.
Mnemonik

Im wyższy poziom izolacji, tym mniej zjawisk współbieżności przechodzi przez sito, ale tym więcej transakcji czeka w kolejce albo dostaje odrzucenie do ponowienia.

Dalej: 05 Normalizacja i świadoma denormalizacja

Normalizacja i świadoma denormalizacja

Normalizacja eliminuje duplikację danych, dzieląc informację na osobne tabele połączone kluczami obcymi. Denormalizacja robi odwrotnie: świadomie kopiuje dane, żeby ograniczyć liczbę JOIN-ów kosztem ryzyka niespójności.

Termin kluczowy

1NF: Każda kolumna przechowuje pojedynczą, atomową wartość, brak list czy zagnieżdżonych struktur w jednej komórce.

Termin kluczowy

2NF: Spełnia 1NF i każda kolumna niekluczowa zależy od całego klucza głównego, nie tylko od jego części (dotyczy kluczy złożonych).

Termin kluczowy

3NF: Spełnia 2NF i żadna kolumna niekluczowa nie zależy od innej kolumny niekluczowej, brak zależności przechodnich. To poziom, na którym w praktyce zatrzymuje się większość schematów OLTP.

To samo zapytanie w schemacie znormalizowanym i zdenormalizowanym sql
-- Znormalizowane (3NF): dane klienta tylko w tabeli customers
SELECT o.id, o.total, c.name, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.id = 1001;

-- Zdenormalizowane: nazwa i e-mail klienta skopiowane do orders
SELECT id, total, customer_name, customer_email
FROM orders
WHERE id = 1001;
Pułapka

Nadmierna normalizacja w systemie transakcyjnym o wysokim ruchu odczytowym prowadzi do zapytań z pięcioma czy sześcioma JOIN-ami tam, gdzie wystarczyłaby jedna tabela raportowa. Każdy dodatkowy JOIN to dodatkowy koszt planowania i wykonania zapytania.

Pułapka

Denormalizacja bez mechanizmu utrzymania spójności (triggera, zadania synchronizującego albo aktualizacji w tej samej transakcji) prowadzi do rozjazdu danych. Skopiowana nazwa klienta w tabeli zamówień nie odzwierciedla zmiany nazwy w tabeli klientów.

  • Warstwa raportowa/analityczna (data warehouse, tabele faktów) czytana dużo częściej niż zapisywana.
  • Pola liczone i cache'owane, np. suma zamówienia zamiast sumowania pozycji przy każdym odczycie.
  • Dane historyczne, które nie powinny się zmieniać nawet gdy zmieni się rekord źródłowy, np. cena produktu w momencie zakupu.
  • Ekstremalnie odczytowe endpointy API, gdzie JOIN w czasie rzeczywistym jest wąskim gardłem.
AspektZnormalizowany schematZdenormalizowany schemat
Ryzyko anomalii aktualizacjiNiskie, dane w jednym miejscuWyższe, wymaga mechanizmu synchronizacji kopii
Wydajność odczytu przy złożonych zapytaniachWymaga JOIN-ów, koszt rośnie z liczbą tabelMniej lub brak JOIN-ów, szybszy pojedynczy odczyt
Zajętość dyskuMniejsza, dane bez duplikacjiWiększa, dane częściowo zduplikowane
Złożoność zapisuProstsza, jeden rekord do zmianyWymaga zaktualizowania wszystkich kopii tej samej wartości
Zasada praktyczna

Projektuj domyślnie w 3NF, denormalizuj punktowo i świadomie tam, gdzie profilowanie pokazało realny problem wydajnościowy, nigdy z góry 'na wszelki wypadek'.

Dalej: 06 Problem N+1 i jak generują go ORM-y

Problem N+1 i jak generują go ORM-y

na egzaminie

N+1 to wzorzec, w którym pobranie listy encji kosztuje jedno zapytanie, a dociągnięcie powiązanych danych dla każdej z nich kosztuje kolejne zapytanie na rekord. Przy 1000 wierszy oznacza to 1001 zapytań zamiast jednego lub dwóch.

Skąd się bierze

ORM-y domyślnie ładują relacje leniwie (lazy loading). Odwołanie do powiązanego obiektu w pętli wyzwala osobne zapytanie SQL dopiero wtedy, a nie z góry. Na małym zbiorze danych różnicy nie widać. Na produkcji, przy tysiącach rekordów, to sekundy albo minuty odpowiedzi.

Termin kluczowy

eager loading (ładowanie zachłanne): Pobranie powiązanych danych z góry, jednym dodatkowym zapytaniem albo jednym JOIN-em, zamiast osobnym zapytaniem na każdy wiersz głównej listy.

Termin kluczowy

lazy loading (ładowanie leniwe): Powiązane dane są pobierane dopiero przy pierwszym odwołaniu do nich w kodzie. Jest to wygodne, ale domyślnie generuje N+1 przy iteracji po liście.

N+1 w ORM-ie i naprawa przez eager loading python
# N+1: 1 zapytanie po listę + N zapytań po powiązane rekordy
orders = Order.objects.all()          # 1 zapytanie
for order in orders:
    print(order.customer.name)        # N zapytań, jedno na każdy order

# Naprawa: eager loading jednym JOIN-em
orders = Order.objects.select_related("customer")  # wciąż 1 zapytanie
for order in orders:
    print(order.customer.name)        # brak dodatkowych zapytań
Pułapka

N+1 rzadko wybucha na etapie code review, bo w testach jednostkowych operuje się na kilku rekordach. Ujawnia się dopiero na produkcyjnym wolumenie danych, często jako 'baza nagle zwolniła' bez żadnej zmiany w kodzie, tylko wzrost liczby wierszy.

  • select_related / prefetch_related (Django), includes (Rails, Prisma): eager loading relacji z góry.
  • DataLoader (batching i cache w obrębie jednego requestu) w GraphQL.
  • Ręczny JOIN i mapowanie wyniku zamiast polegania na leniwym ładowaniu ORM-a.
  • Denormalizacja albo cache dla relacji odczytywanych bardzo często i rzadko zmienianych.
Liczba wierszy listyZapytań przy lazy loadingZapytań przy eager loading
10111
1 0001 0011
100 000100 0011
  1. Włącz logowanie wykonywanych zapytań SQL W środowisku deweloperskim, żeby zobaczyć realną liczbę zapytań na request.
  2. Policz zapytania na request Django Debug Toolbar, gem Bullet w Rails, albo APM typu New Relic/Datadog.
  3. Testuj na realistycznym wolumenie danych Nie na trzech rekordach, N+1 jest niewidoczny przy małym zbiorze.
  4. Ustaw alert na liczbę zapytań per request W monitoringu produkcyjnym, żeby wykryć regresję zanim zgłoszą ją użytkownicy.
Mnemonik

N+1 to zawsze ten sam kształt: 1 zapytanie po listę, plus N zapytań w pętli po szczegóły. Powtarzający się identyczny SELECT z inną wartością w WHERE w logu to właśnie ten wzorzec.

Dalej: 07 SQL injection i parametryzacja zapytań

SQL injection i parametryzacja zapytań

na egzaminie

SQL injection powstaje, gdy dane od użytkownika trafiają bezpośrednio do treści zapytania SQL i zmieniają jego strukturę, a nie tylko wartość. Jedyna realna obrona to parametryzacja: oddzielenie kodu zapytania od danych na poziomie sterownika bazy, a nie ręczne escapowanie stringów.

Mechanizm ataku

Baza danych nie odróżnia 'kodu' od 'danych' w gotowym stringu zapytania. Gdy username wstawiony przez konkatenację brzmi ' OR '1'='1, warunek WHERE username = '' OR '1'='1' jest zawsze prawdziwy i zwraca wszystkie wiersze, niezależnie od intencji zapytania.

Podatna konkatenacja kontra bezpieczne zapytanie parametryzowane python
# PODATNE: string interpolation buduje zapytanie z danych użytkownika
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
# username = "' OR '1'='1" zwraca wszystkich użytkowników

# BEZPIECZNE: parametryzowane zapytanie, silnik bazy oddziela kod od danych
cursor.execute(
    "SELECT * FROM users WHERE username = %s",
    (username,)
)
Termin kluczowy

prepared statement / zapytanie parametryzowane: Zapytanie wysyłane do bazy z placeholderami (?, %s, $1 zależnie od sterownika), a wartości przekazywane osobno. Silnik bazy traktuje parametry wyłącznie jako dane, nigdy jako fragment składni SQL.

Pułapka

Escapowanie cudzysłowów czy ręczna 'sanityzacja' stringa nie jest równoważna parametryzacji. Łatwo pominąć edge case (kodowanie znaków, kontekst zagnieżdżony). Wystarczy jedno przeoczone miejsce, żeby atak przeszedł.

Pułapka

ORM chroni domyślnie tylko wtedy, gdy używa się jego standardowego API. Metody typu raw() (Django), query() z surowym stringiem czy ręcznie budowany fragment WHERE przez konkatenację są równie podatne jak czyste SQL bez ORM-a.

  • Zasada najmniejszych uprawnień: konto aplikacji nie ma prawa DROP/ALTER, tylko operacje potrzebne do działania.
  • Walidacja typu i formatu danych wejściowych przed dotarciem do warstwy zapytań.
  • Logowanie i alerty na nietypowe wzorce zapytań oraz błędy składni SQL zwracane do klienta.
  • Web Application Firewall jako dodatkowa warstwa, nigdy jako jedyna linia obrony.
Mnemonik

Konkatenacja stringów do zapytania SQL to zawsze czerwona flaga, niezależnie od tego, jak 'zaufane' wydaje się źródło danych. Parametr, nigdy string.

WzorzecBezpieczny?Dlaczego
Konkatenacja stringów (f-string, +)NieDane wejściowe stają się częścią składni SQL
Ręczne escapowanie znaków specjalnychCzęściowo, niewystarczającoŁatwo pominąć edge case, brak gwarancji na poziomie silnika bazy
ORM: metoda raw()/surowy string bez parametrówNieOmija bezpieczny query builder, działa jak konkatenacja
Zapytanie parametryzowane / prepared statementTakDane przekazywane osobno od kodu zapytania
ORM: standardowy query builder (filter/where)TakWewnętrznie generuje zapytanie parametryzowane
Rekomendacja branżowa

OWASP od lat wskazuje parametryzację zapytań (prepared statements) jako podstawowy, obowiązkowy mechanizm obrony przed SQL injection. Dotyczy to wszystkich języków i sterowników baz danych. OWASP stawia ją przed escapowaniem i przed walidacją wejścia.

Dalej: 08 SQL vs NoSQL: kryteria decyzji

SQL vs NoSQL: kryteria decyzji

Wybór między bazą relacyjną a NoSQL to decyzja, gdzie ponieść koszt. Albo przy zapisie (silny schemat, spójność, JOIN-y), albo przy odczycie i skalowaniu poziomym (elastyczny schemat, replikacja, częściowa spójność).

SQL (relacyjne)

  • Silny, wymuszony schemat i typy danych
  • Transakcje ACID na wielu tabelach
  • JOIN-y jako natywna operacja łącząca dane
  • Dojrzała integralność referencyjna (klucze obce)
  • Skalowanie głównie pionowe, poziome trudniejsze (ręczny sharding)

NoSQL (dokumentowe, kolumnowe, grafowe, klucz-wartość)

  • Elastyczny lub brak schematu (schema-on-read)
  • Natywne skalowanie poziome i replikacja między regionami
  • Zwykle brak JOIN-ów, dane denormalizowane z góry w dokumencie
  • Model spójności często eventual consistency zamiast pełnej ACID
  • Wyspecjalizowany wybór pod kształt danych: graf, szereg czasowy, cache
Termin kluczowy

eventual consistency (spójność ostateczna): Po zapisie różne repliki bazy mogą przez krótki czas zwracać różne wartości tego samego rekordu, zanim zmiana się rozpropaguje. Akceptowalne dla licznika polubień, nieakceptowalne dla salda konta bankowego.

Termin kluczowy

schema-on-write vs schema-on-read: Baza relacyjna wymusza strukturę w momencie zapisu: błędny typ danych odrzuca INSERT. Wiele baz dokumentowych sprawdza strukturę dopiero w momencie odczytu przez aplikację, co daje elastyczność kosztem przesunięcia błędów w czasie.

CAP theorem w skrócie

Przy podziale sieci (Partition) system rozproszony musi wybrać między pełną Spójnością (Consistency) a Dostępnością (Availability) dla części, która utraciła łączność. Bazy relacyjne na jednym węźle rzadko muszą rozstrzygać ten kompromis. Systemy NoSQL na wiele regionów muszą to zrobić świadomie.

Dokument w bazie NoSQL: dane zamówienia zagnieżdżone w jednym rekordzie json
{
  "_id": "order_1001",
  "customer": { "name": "Jan Kowalski", "email": "jan@example.com" },
  "items": [
    { "sku": "ABC123", "qty": 2, "price": 49.99 },
    { "sku": "XYZ789", "qty": 1, "price": 129.00 }
  ],
  "status": "paid"
}
Pułapka

Wybór NoSQL 'pod skalę', zanim ktokolwiek zmierzył realne wąskie gardło dobrze zaindeksowanej bazy relacyjnej, to częsty przypadek przedwczesnej optymalizacji. Jeden dobrze skonfigurowany PostgreSQL obsługuje miliony wierszy i tysiące zapytań na sekundę, zanim w ogóle trzeba myśleć o shardingu.

  • Czy dane mają naturalną strukturę relacyjną z wieloma powiązaniami wymagającymi JOIN-ów, czy raczej kształt pojedynczego samodzielnego dokumentu?
  • Czy potrzebujesz transakcji ACID obejmujących wiele rekordów lub tabel naraz?
  • Jaka jest realna skala zapisu i czy pojedynczy węzeł bazy relacyjnej z odpowiednim indeksowaniem faktycznie jej nie udźwignie?
  • Czy dopuszczalna jest chwilowa niespójność między replikami, czy dana domena (płatności, stany magazynowe) tego nie toleruje?
Charakter obciążeniaRekomendacjaPrzykład technologii
Transakcje finansowe z wieloma powiązanymi tabelamiSQLPostgreSQL, MySQL
Katalog produktów o zmiennej, niejednorodnej strukturzeNoSQL dokumentoweMongoDB
Sesje i cache o krótkim czasie życiaNoSQL klucz-wartośćRedis
Sieć powiązań (rekomendacje, graf społecznościowy)NoSQL grafoweNeo4j
Metryki i logi w czasieBaza szeregów czasowychTimescaleDB, InfluxDB

Sprawdź się

20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.

Rozpocznij quiz

Fiszki: SQL i bazy danych

Pytanie

O tym demo

Działające demo kursów napisanych przez Tenzan Logic. Wyniki zostają na tym urządzeniu, bez konta.

Każdy kurs, pytanie i fiszkę napisał Tenzan Logic, po polsku i od zera. Jeśli chcesz taki zestaw dla swojego zespołu, z Twoimi procedurami i Twoim słownictwem, napisz.

Napisz do Tenzan Logic

Jak to działa

100% czas (dni) bez powtórek 1 3 7 21 z powtórkami
Schemat idei, nie dane pomiarowe (Ebbinghaus 1885; Cepeda i in. 2006).
  • Odstępy

    1·3·7·21 dni do powtórki

    Fiszka z „Umiem” wraca po 1, 3, 7 i 21 dniach.

    Badanie

    Powtórki z przerwami dają trwalszą pamięć niż jedna po drugiej. Cepeda i in. 2006, Psychological Bulletin 132(3): 354-380

  • Przypominanie

    ukryta odpowiedź fiszki

    Najpierw odpowiedz z pamięci, potem odwróć.

    Badanie

    Próba przypomnienia dała lepszą pamięć po dwóch dniach i po tygodniu niż ponowne czytanie. Roediger i Karpicke 2006, Psychological Science 17(3): 249-255

  • Dwa kody

    4 kolory bloków

    Fakt, termin, mnemonik i pułapka mają swój kolor.

    Badanie

    Teoria: słowo i obraz zapisują się osobno. To teoria, nie gwarancja wyniku. Paivio 1991, Canadian Journal of Psychology 45(3): 255-287

  • Porcje

    167 krótkich sekcji

    Jedna sekcja to jedna porcja do zapamiętania.

    Badanie

    Pamięć robocza mieści około czterech porcji (Miller 1956: siedem, Cowan 2001: cztery). Cowan 2001, Behavioral and Brain Sciences 24(1): 87-114

  • Skróty

    134 zielone bloki

    Mnemonik zbiera kilka elementów w jeden, np. „SUR”.

    Badanie

    Skrót zbiera kilka elementów w jeden; przykład „SUR” jest w kursie o podstawach AI. Opis funkcji aplikacji, bez cytowanego badania

  • Testy

    20 z 24 pytań w quizie

    Po każdej odpowiedzi widzisz wyjaśnienie.

    Badanie

    Ćwiczenie, nie sprawdzian: pytania są w losowej kolejności i uzupełniają fiszki, nie zastępują ich. Efekt testowania: zob. „Przypominanie”

Szukaj

Postęp

Brak wyników. Rozwiąż pierwszy quiz.

Tu zbiera się Twój postęp: rekord i medal każdego quizu, seria dni i pytania do powtórki.

Gdzie jest zapis
Tylko w pamięci tej przeglądarki. Konta nie ma i wyniki nigdzie nie są wysyłane. Inna przeglądarka lub inne urządzenie zaczyna od zera.
Jak wyczyścić
W quizie: Historia, potem Wyczyść historię. Fiszki i serię dni usuniesz razem z danymi witryny w ustawieniach przeglądarki.
Wybierz quiz

Progi medali

Medal liczy się z najlepszego wyniku quizu: Brąz od 60%, Srebro od 75%, Złoto od 90%, Platyna przy 100%.