Zła baza danych to fundament, który prędzej czy później posypie się pod ciężarem aplikacji. Kilka decyzji podjętych na początku projektu potrafi zaoszczędzić miesięcy pracy – albo kosztować je w przyszłości.
Zacznij od modelu danych, nie od tabel
Największy błąd, jaki popełniają początkujący projektanci baz danych, to siadanie od razu do tworzenia tabel. Zanim napiszesz pierwszą instrukcję CREATE TABLE, musisz dokładnie rozumieć domenę problemu – jakie encje istnieją w systemie, jakie mają atrybuty i jak się ze sobą łączą.
Narzędziem do tego jest diagram ERD (Entity-Relationship Diagram). Rysujesz na nim encje jako prostokąty, relacje między nimi jako linie, a przy każdej relacji oznaczasz jej krotność – czy jeden użytkownik może mieć wiele zamówień, czy jedno zamówienie może należeć do wielu użytkowników. Ten diagram staje się kontraktem między tobą a resztą zespołu i fundamentem wszystkich późniejszych decyzji.
Normalizacja – porządek, który chroni dane
Normalizacja to proces eliminowania redundancji z bazy danych. Mówi się o niej w kontekście tzw. postaci normalnych – pierwszej, drugiej, trzeciej i dalszych. W praktyce większość aplikacji webowych potrzebuje trzeciej postaci normalnej i na niej można poprzestać.
Co to oznacza w praktyce? Każda informacja powinna być przechowywana dokładnie w jednym miejscu. Jeśli adres klienta pojawia się w tabeli klientów i w każdym jego zamówieniu osobno – to błąd. Gdy klient zmieni adres, musisz aktualizować go w dziesiątkach rekordów, ryzykując niespójność danych. Znormalizowana baza przechowuje adres raz, a zamówienie jedynie odwołuje się do klienta przez klucz obcy.
Normalizacja ma jednak swoją cenę – zapytania wymagają łączenia wielu tabel (JOIN), co przy dużych zbiorach danych może wpływać na wydajność. Dlatego w systemach analitycznych i hurtowniach danych świadomie denormalizuje się struktury, żeby przyspieszyć odczyt kosztem redundancji. To nie błąd – to świadomy kompromis.
Klucze, indeksy i wydajność zapytań
Klucz główny (PRIMARY KEY) to fundament każdej tabeli – unikalny identyfikator każdego rekordu. Najczęściej używa się liczb całkowitych z auto-inkrementacją albo UUID. Oba podejścia mają swoje wady i zalety: liczby całkowite są mniejsze, szybsze w indeksowaniu i czytelniejsze w logach. UUID są globalnie unikalne, co ułatwia replikację i scalanie danych z różnych źródeł, ale zajmują więcej miejsca i fragmentują indeksy.
Indeksy to struktury przyspieszające wyszukiwanie w bazie – działają jak spis treści w książce. Bez indeksu baza musi przejrzeć każdy rekord w tabeli, żeby znaleźć pasujące wiersze. Z indeksem robi to w ułamku czasu. Dodaj indeksy na kolumnach, po których często filtrujesz, sortujesz lub łączysz tabele – czyli na kluczach obcych, polach używanych w klauzulach WHERE i kolumnach z datami, jeśli często pobierasz dane z przedziałów czasowych.
Pułapka: zbyt wiele indeksów spowalnia operacje zapisu. Każdy INSERT, UPDATE i DELETE musi zaktualizować wszystkie indeksy na danej tabeli. Indeksuj mądrze, nie wszystko.
Typy danych – precyzja ma znaczenie
Wybór właściwego typu danych dla każdej kolumny to decyzja, która wpływa zarówno na integralność danych, jak i na wydajność bazy. Kilka zasad, które warto zapamiętać.
Daty i godziny przechowuj zawsze w dedykowanych typach – DATE, DATETIME lub TIMESTAMP – nigdy jako tekst. Tekst nie pozwala na operacje arytmetyczne na datach, nie sortuje się poprawnie i nie waliduje poprawności wartości.
Dla wartości pieniężnych używaj DECIMAL zamiast FLOAT. Typ zmiennoprzecinkowy wprowadza błędy zaokrągleń, które w kontekście finansowym są niedopuszczalne. Różnica między 99.99 a 99.98999999 jest niewidoczna gołym okiem, ale katastrofalna w bilansach.
Rozmiar kolumn tekstowych dobieraj świadomie. VARCHAR(255) dla każdego pola tekstowego to lenistwo, nie standard. Kod pocztowy nigdy nie przekroczy kilku znaków, numer telefonu ma określony format, kod ISO kraju to dokładnie 2 znaki. Precyzyjne typy chronią przed błędnymi danymi i pomagają bazie optymalizować przechowywanie.
Integralność danych – baza jako ostatnia linia obrony
Aplikacja może mieć błędy. Migracje mogą pójść nie tak. Ktoś może bezpośrednio edytować bazę przez klienta SQL. Dlatego nigdy nie polegaj wyłącznie na walidacji po stronie aplikacji – baza danych powinna sama pilnować spójności swoich danych.
Klucze obce (FOREIGN KEY) wymuszają referencyjną integralność – nie możesz dodać zamówienia dla klienta, który nie istnieje, ani usunąć klienta, który ma aktywne zamówienia. Ograniczenia NOT NULL pilnują, żeby kluczowe pola nigdy nie pozostały puste. Ograniczenia UNIQUE zapobiegają duplikatom tam, gdzie są niedopuszczalne – np. dwa konta z tym samym adresem e-mail. Ograniczenia CHECK pozwalają zdefiniować własne reguły – np. że cena produktu musi być wartością dodatnią.
Te mechanizmy są bezpłatne i działają niezależnie od tego, co robi aplikacja. Warto z nich korzystać.
Myśl o przyszłości, projektując na dziś
Bazy danych żyją długo – często dłużej niż aplikacje, które je używają. Schema zaprojektowana dziś będzie modyfikowana przez lata, a każda zmiana struktury w produkcyjnej bazie z milionami rekordów to operacja wymagająca starannego planowania i okna serwisowego.
Kilka zasad, które ułatwiają życie w przyszłości: nie usuwaj rekordów – zamiast tego dodaj kolumnę deleted_at i filtruj po niej (soft delete). Zawsze dodawaj kolumny created_at i updated_at do każdej tabeli – historia zmian jest bezcenna przy debugowaniu. Unikaj przechowywania danych wyliczalnych – suma zamówień, wiek użytkownika czy liczba komentarzy powinny być obliczane w zapytaniu, nie przechowywane jako osobne kolumny, bo zawsze mogą się zdezsynchronizować z rzeczywistością.
Dobra baza danych to taka, która za dwa lata – gdy w projekcie nie będzie już nikogo z obecnego zespołu – będzie nadal zrozumiała, spójna i łatwa do rozszerzenia. To cel, który warto mieć w głowie od pierwszego diagramu.