Optymalizacja zapytań w bazach danych: jak poprawić wydajność?

Optymalizacja zapytań w bazach danych: jak poprawić wydajność? - 1 2025

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:

  1. Indeksuj tylko kolumny używane w WHERE, JOIN i ORDER BY
  2. Dla zapytań z wieloma warunkami rozważ indeksy złożone
  3. 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
List users = 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

  1. Mierz zanim poprawisz – EXPLAIN to twój przyjaciel
  2. Indeksuj z głową – więcej ≠ lepiej
  3. Łącz z rozwagą – najpierw ogranicz zbiory
  4. Cache’uj agresywnie – Redis to twój sojusznik
  5. Batchuj operacje – 1000 INSERTów naraz zamiast pojedynczo
  6. Śledź wolne zapytania – włącz slow query log
  7. Zaglądaj pod maskę ORM – wiedza to moc
  8. Dopasuj typy danych – SMALLINT zamiast INT tam gdzie możesz
  9. Rozważ partycjonowanie – dla tabel >10GB
  10. 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.