Kiedy idealnie normalizujemy dane, każda tabela staje się maksymalnie kompaktowa i informacje w niej odpowiadają tylko jednej zasadzie. Ale żeby wykonać realne zapytania (np. "Którzy studenci zapisali się na kurs SQL?") trzeba połączyć masę tabel. Im więcej tabel, tym bardziej skomplikowane zapytania, tym więcej system "kopie łopatą".
Pewnie już znasz JOIN-y z poprzednich wykładów. Oto przykład zapytania, które może się przydać przy pracy z dobrze zaprojektowaną bazą danych:
SELECT students.name, courses.title
FROM students
JOIN enrollments ON students.id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.id
WHERE courses.title = 'SQL';
Brzmi prosto, ale pod maską serwer robi piekielną robotę: czyta każdą tabelę, łączy dane, filtruje... A co jeśli tabele są naprawdę ogromne? Logiczne, że wydajność będzie spadać.
Wojny: normalizacja kontra szybkość
Na szczęście (albo niestety?), w prawdziwym życiu bazy danych to kompromis. Pełna normalizacja daje spójność danych, ale spowalnia złożone zapytania. Jeśli baza będzie używana do analityki i raportów, czasem lepiej ją zdenormalizować. To jak zamienić 10 małych pudełek na jedną dużą skrzynię: szybciej wyciągniesz dane, ale trudniej je potem rozłożyć z powrotem.
Kiedy można "odpuścić" z normalizacją?
Są scenariusze, gdzie denormalizacja jest lepsza:
Często używane agregaty
Na przykład wyobraź sobie, że system codziennie liczy ilu studentów jest na każdym kursie. W znormalizowanej strukturze trzeba ciągle robićJOIN i
COUNT(). Zamiast tego można dodać do tabeli "Courses" kolumnę
student_count, która aktualizuje się automatycznie przy dodaniu/usunięciu wpisów.
-- Zdenormalizowana kolumna
UPDATE courses
SET student_count = (
SELECT COUNT(*)
FROM enrollments
WHERE enrollments.course_id = courses.id
);
Często generowane raporty
Jeśli twój klient codziennie chce raport "Kto, co, kiedy kupił?", łatwiej trzymać zdenormalizowaną tabelę z gotowymi wierszami typu "Imię klienta, produkt, data". Główna tabela będzie większa, ale szybkość pobierania danych wzrośnie.
Dużo odczytów, mało zapisów
Kiedy baza jest głównie do odczytu (np. analityka), warto poświęcić normalizację dla szybkości.
Minimalizacja joinów przy złożonych relacjach
Jeśli między tabelami są wielowarstwowe (nested) relacje iJOIN to koszmar, usuń kilka poziomów normalizacji.
Przykład: jak denormalizacja przyspiesza działanie?
Mamy znormalizowane tabele sklepu internetowego:
Tablica products |
Tablica orders |
Tablica order_items |
|---|---|---|
| id | id | id |
| name | date | order_id |
| price | customer_id | product_id |
| quantity |
Każde zamówienie (orders) składa się z pozycji zamówienia (order_items). Policzmy ile pieniędzy zarobił sklep:
SELECT SUM(order_items.quantity * products.price) AS total_revenue
FROM order_items
JOIN products ON order_items.product_id = products.id;
Proces łączenia order_items i products spowolni zapytanie przy dużych ilościach danych.
Zdenormalizowana struktura
Wyobraź sobie teraz, że w tabeli order_items jest "dodatkowa" kolumna total_price (denormalizacja):
Tablica order_items |
|---|
| id |
| order_id |
| product_id |
| quantity |
| total_price |
Teraz zapytanie jest banalne:
SELECT SUM(total_price) AS total_revenue
FROM order_items;
Unikamy JOIN, więc przyspieszamy wykonanie.
Zadanie praktyczne: optymalizacja bazy "Sprzedaż"
Dane: znormalizowane tabele
Tablica products |
Tablica sales |
|---|---|
| id | id |
| name | product_id |
| price | date |
| quantity |
Zadanie: przyspieszyć częste zapytania typu "Ile zarobiliśmy na każdym produkcie?".
Krok 1: dodajemy kolumnę total_price do tabeli sales:
ALTER TABLE sales ADD COLUMN total_price NUMERIC;
Krok 2: wypełniamy tę kolumnę dla istniejących danych:
UPDATE sales
SET total_price = quantity * (
SELECT price
FROM products
WHERE products.id = sales.product_id
);
Krok 3: zapytania są szybsze:
SELECT product_id, SUM(total_price) AS total_revenue
FROM sales
GROUP BY product_id;
Ale! Denormalizacja ma swoją cenę
Wiesz, że "szybciej" nie zawsze znaczy "lepiej". Przy denormalizacji pojawiają się problemy:
Nadmiarowe przechowywanie
Kolumnatotal_price to kopia danych, która zajmuje dodatkowe miejsce.
Trudność aktualizacji
Jeśli cena produktu zmieni się w tabeli products, musimy ręcznie zaktualizować odpowiednią kolumnę total_price. To może prowadzić do niespójności.
Anomalie przy wstawianiu, aktualizacji i usuwaniu
Informacje łatwo się "rozjeżdżają", jeśli zapomnisz zaktualizować zdenormalizowane dane. Na przykład, jeśli zmienia się cena produktu, nie dzieje się to automatycznie.
Balans: jak znaleźć złoty środek?
Wybierz, co ważniejsze: wydajność czy struktura? Jeśli baza jest mocno czytana, dostosuj się do zapytań.
Dodawaj denormalizację punktowo. Na przykład tylko dla kluczowych liczb i raportów.
Automatyzuj aktualizacje zdenormalizowanych danych. Używaj triggerów lub zadań, żeby uniknąć niespójności.
GO TO FULL VERSION