Optymalizacja zapytań SQL: Jak wycisnąć z bazy maksimum wydajności
Pamiętam tę noc jak dziś. Aplikacja kluczowego klienta zaczęła się dusić, a średni czas odpowiedzi przekroczył 8 sekund. Po przeglądzie logów okazało się, że jeden zapytanie wykonywało się średnio 2,3 sekundy – i było wywoływane 40 razy na pojedynczą operację. To był moment, gdy nauczyłem się, że optymalizacja zapytań to nie teoria, a codzienność każdego developera.
Indeksy – nie tędy droga
Podczas konsultacji dla pewnego startupu znalazłem tabelę z 22 indeksami na 15 kolumnach. Każdy INSERT zajmował średnio 47ms. Po usunięciu nadmiarowych indeksów czas spadł do 3ms, a selecty i tak działały błyskawicznie.
Jak dobrać właściwe indeksy? Praktyczne zasady:
- Indeksuj tylko kolumny używane w WHERE, JOIN i ORDER BY
- Dla zapytań z wieloma warunkami rozważ indeksy złożone
- Unikaj indeksowania kolumn o niskiej selektywności (jak status z 3 wartościami)
EXPLAIN – twój detektor kłamstw
W PostgreSQL polecam to podejście:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE last_active > NOW() - INTERVAL '30 days';
Ostatnio znalazłem dzięki temu zapytanie, które:
- Skanowało 1.2 miliona wierszy
- Zużywało 348MB pamięci
- Wykonywało się 1.8 sekundy
Po dodaniu brakującego indeksu czas spadł do 23ms. Moralność? Nigdy nie zgaduj, zawsze mierz.
JOIN-y po chirurgicznej strzyży
Typowy przypadek z mojej praktyki:
-- Przed optymalizacją (12 sekund) SELECT o.*, c.* FROM orders o JOIN customers c ON o.customer_id = c.id WHERE c.status = 'vip' AND o.created_at > '2023-01-01' -- Po optymalizacji (0.4 sekundy) SELECT o.* FROM orders o JOIN (SELECT id FROM customers WHERE status = 'vip') c ON o.customer_id = c.id WHERE o.created_at > '2023-01-01'
Trik polega na:
- Najpierw ograniczyć zbiór danych
- Wybrać tylko niezbędne kolumny
- Unikać złączania całych tabel
Pula połączeń – nie popełniaj tego błędu
W jednej aplikacji widziałem tworzenie nowego połączenia dla każdego zapytania. Efekt? 300ms narzutu na każde żądanie. Po wdrożeniu puli połączeń (HikariCP) czas spadł do 5ms.
Ustawienia, które sprawdziły się w produkcji:
# Dla aplikacji z ~500 równoczesnych użytkowników minimumIdle: 5 maximumPoolSize: 50 connectionTimeout: 30000 idleTimeout: 600000
ORM – jak nie strzelić sobie w stopę
Problem N+1 to klasyka. Przykład z Hibernate:
// ŹLE: 101 zapytań dla 100 użytkowników Listusers = session.createQuery(FROM User).list(); for (User user : users) { System.out.println(user.getOrders().size()); // Lazy loading } // DOBRZE: 1 zapytanie z JOIN FETCH List users = session.createQuery( SELECT u FROM User u JOIN FETCH u.orders).list();
Najczęstsze grzechy ORM:
Błąd | Naprawa |
---|---|
N+1 queries | Eager loading lub JOIN FETCH |
SELECT * | Wybór konkretnych kolumn |
Nieużywanie batchowania | hibernate.jdbc.batch_size |
Denormalizacja – kiedy warto złamać zasady
W systemie e-commerce mieliśmy zapytanie:
SELECT p.*, (SELECT COUNT(*) FROM order_items WHERE product_id = p.id) as sales_count FROM products p WHERE category = 'electronics'
Rozwiązanie? Pole sales_count w tabeli products aktualizowane triggerem. Czas zapytania spadł z 1200ms do 25ms.
Kiedy warto denormalizować:
- Częste, kosztowne agregacje
- Dane raportowe
- Statystyki wyświetlane obok głównego rekordu
Type matters – małe różnice, duże efekty
Prawdziwa historia: tabela z 10 milionami rekordów miała kolumnę ip_address jako VARCHAR(15). Po zmianie na INT (4 bajty) i przechowywaniu IP jako liczby:
- Rozmiar indeksu spadł z 320MB do 85MB
- Zapytania przyspieszyły o ~40%
- Backupy były o 15% mniejsze
moje 10 przykazań optymalizacji
- Mierz zanim poprawisz – EXPLAIN to twój przyjaciel
- Indeksuj z głową – więcej ≠ lepiej
- Łącz z rozwagą – najpierw ogranicz zbiory
- Cache’uj agresywnie – Redis to twój sojusznik
- Batchuj operacje – 1000 INSERTów naraz zamiast pojedynczo
- Śledź wolne zapytania – włącz slow query log
- Zaglądaj pod maskę ORM – wiedza to moc
- Dopasuj typy danych – SMALLINT zamiast INT tam gdzie możesz
- Rozważ partycjonowanie – dla tabel >10GB
- Denormalizuj strategicznie – ale z planem aktualizacji
Optymalizacja to nie jednorazowy projekt, a sposób myślenia. Zacznij od krytycznych ścieżek, mierz efekty i stopniowo poprawiaj resztę. Pamiętaj – każde 100ms oszczędzone na zapytaniach pomnożone przez tysiące użytkowników daje godziny zaoszczędzonego czasu.