Przejdź do głównej zawartości

Wzorce optymalizacji SQL

Wzorce optymalizacji SQL obejmują podłączenie serwera MCP bazy danych do Cursora, Claude Code lub Codeksa, dzięki czemu asystent czyta prawdziwy plan wykonania, prawdziwe statystyki tabel i prawdziwe użycie indeksów zamiast zgadywać, a następnie zamienia te dane w uszeregowane poprawki wąskich gardeł, rekomendacje indeksów i przepisania podzapytań na funkcje okna zachowujące wyniki.

Zapytanie do dashboardu, które na Twoim laptopie wykonywało się w 40 ms, w produkcji przekracza limit czasu po 12 sekundach. Tabela urosła z 50 tys. wierszy do 80 mln, optymalizator po cichu przełączył się na skanowanie sekwencyjne, a Twój APM świeci się na czerwono. Wklejasz zapytanie do asystenta AI i dostajesz pewną siebie odpowiedź „dodaj indeks na customer_id” — tyle że ten indeks już istnieje, a planner i tak go ignoruje.

Rozwiązaniem jest przestać zgadywać. Kiedy podłączysz serwer MCP bazy danych do Cursora, Claude Code lub Codeksa, asystent czyta Twój rzeczywisty plan wykonania, Twoje rzeczywiste statystyki tabel i Twoje rzeczywiste użycie indeksów — a potem proponuje zmiany, które możesz zweryfikować na prawdziwych liczbach zamiast na ludowych wierzeniach.

  • Podłączony serwer MCP Postgresa, dzięki któremu asystent czyta żywy schemat i wynik EXPLAIN zamiast halucynować
  • Gotowy prompt, który zamienia zrzut EXPLAIN (ANALYZE, BUFFERS) w uporządkowaną listę wąskich gardeł
  • Prompt rekomendujący indeksy na podstawie rzeczywistych wzorców zapytań — i odrzucający te zbędne
  • Prompt przepisujący podzapytanie na funkcję okna z zachowaniem wyników
  • Jasne wyczucie, kiedy rady AI wprowadzą Cię w błąd (małe tabele, zmaterializowane CTE, efekty uboczne EXPLAIN ANALYZE)

Bez serwera MCP asystent rozumuje o schemacie, którego nie widzi. Z nim może uruchomić EXPLAIN, zajrzeć do pg_stat_user_indexes i sprawdzić statystyki kolumn bezpośrednio. Konfiguracja jest identyczna w Cursorze, Claude Code i Codeksie — wszystkie czytają tę samą konfigurację MCP; różni się tylko lokalizacja pliku.

Do pracy lokalnej i operacji świadomych schematu Prisma Postgres MCP jest dostarczany w ramach Prisma CLI i nie wymaga dodatkowej instalacji:

{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "prisma", "mcp"]
}
}
}

Wrzuć konfigurację do .cursor/mcp.json (Cursor), zarejestruj ją przez claude mcp add (Claude Code) lub dodaj do ~/.codex/config.toml w sekcji [mcp_servers.postgres] (Codex). Potem opisany niżej przepływ pracy jest wszędzie taki sam.

Pojedynczym ruchem o największej dźwigni jest podanie asystentowi prawdziwego planu EXPLAIN (ANALYZE, BUFFERS) i poproszenie go o uszeregowanie problemów według kosztu. Ogólne prompty „zoptymalizuj to zapytanie” dają ogólne porady; prompt oparty na planie daje konkrety.

To właśnie więzy oparte na danych — „sprawdź pg_stats”, „nie sugeruj istniejących indeksów”, „oznacz Rows Removed by Filter” — odróżniają tę odpowiedź od tego, co zwróciłaby wyszukiwarka. Rozjazd szacunku liczby wierszy o 10x niemal zawsze oznacza nieaktualne statystyki (ANALYZE tabeli), a nie brakujący indeks — i asystent wychwyci to tylko wtedy, gdy każesz mu spojrzeć.

Gdy plan wskaże na skanowanie, kolejne pytanie brzmi: który indeks — i czy już nie masz takiego, który wykonuje to zadanie. Tu czytanie pg_stat_user_indexes bije intuicję: zespoły rutynowo dźwigają po kilkanaście nieużywanych indeksów, które spowalniają każdy zapis.

W trybie Agent, z podłączonym Postgres MCP, dołącz schemat i folder migracji jako kontekst i pozwól agentowi zarówno zdiagnozować, jak i napisać migrację:

@migrations Using the Postgres MCP, query pg_stat_user_indexes and
pg_stat_user_tables for the orders and order_items tables. Then:
- List indexes with idx_scan = 0 over the last stats window (candidates to drop)
- Propose the minimal set of indexes for the WHERE/JOIN/ORDER BY in
the slow dashboard query, preferring one composite index over several
single-column ones where the leading column is selective
- Write the CREATE INDEX CONCURRENTLY statements as a new migration file
- Estimate the write-amplification cost of each new index

Wzorce przepisywania, z których optymalizator Cię nie wybawi

Dział zatytułowany „Wzorce przepisywania, z których optymalizator Cię nie wybawi”

Część wolnych zapytań to nie problem indeksowania — to problem kształtu. Skorelowane podzapytania, które uruchamiają się ponownie dla każdego wiersza, to klasyczny przypadek, a lekarstwem jest funkcja okna. Zawsze proś asystenta, by udowodnił, że przepisana wersja zwraca identyczne wiersze.

Asystent powinien zwrócić oczywistą postać SUM(...) OVER (PARTITION BY customer_id ORDER BY order_date) — ale to weryfikacja EXCEPT w obie strony jest tym, co naprawdę się liczy. Domyślne ramki okna różnią się od naiwnego podzapytania (RANGE kontra ROWS, obsługa remisów przy równym order_date), a po cichu zmienione wyniki to najdroższy rodzaj „optymalizacji”.

Konkretnie dla PostgreSQL 18 dopominaj się o właściwy typ indeksu pod dany wzorzec dostępu, zamiast wszędzie wstawiać B-tree:

-- Indeks częściowy dla gorącej ścieżki (tylko aktywne wiersze) — mniejszy, szybszy
CREATE INDEX CONCURRENTLY idx_active_customers
ON customers (customer_id) WHERE status = 'active';
-- GIN dla zawierania JSONB / wyszukiwania pełnotekstowego
CREATE INDEX CONCURRENTLY idx_product_attributes
ON products USING gin (attributes);
-- BRIN dla danych naturalnie uporządkowanych w czasie, tylko-do-dopisywania — maleńki na dysku
CREATE INDEX CONCURRENTLY idx_events_created_at
ON events USING brin (created_at);

Strategiczna denormalizacja dla ścieżek ciężkich odczytowo

Dział zatytułowany „Strategiczna denormalizacja dla ścieżek ciężkich odczytowo”

Gdy złączenie jest gorące, a leżące pod nim wiersze rzadko się zmieniają, utrzymywana tabela podsumowań bije ponowne agregowanie przy każdym żądaniu. Poproś asystenta o wygenerowanie zarówno tabeli, jak i wyzwalacza, który utrzymuje ją w poprawnym stanie — to o wyzwalaczu ludzie zapominają, a nieaktualne podsumowanie jest gorsze niż wolne zapytanie.

Subtelna pułapka dokładności: APPROX_COUNT_DISTINCT() nie jest natywną funkcją PostgreSQL ani MySQL. Istnieje w BigQuery, Snowflake i Sparku, więc sugestie AI, które ją zawierają, na czystym Postgresie rzucą błędem function approx_count_distinct does not exist.

  • BigQuery / Snowflake: APPROX_COUNT_DISTINCT(customer_id) jest natywne — używaj śmiało.
  • PostgreSQL: zainstaluj rozszerzenie hll i używaj hll_cardinality(hll_add_agg(hll_hash_bigint(customer_id))) albo pogódź się z dokładnym COUNT(DISTINCT ...) wspartym indeksem pokrywającym.
  • MySQL: nie ma wbudowanego przybliżonego liczenia odrębnych wartości; użyj dokładnego COUNT(DISTINCT ...) lub utrzymuj tabelę licznikową.

To dokładnie ten rodzaj błędu między silnikami, któremu zapobiega podłączony serwer MCP — asystent może potwierdzić, że funkcja istnieje, zanim ją zaproponuje.