 Autor: [Monika Wojciechowska](/autorzy/monika-wojciechowska) Specjalistka SEO i treści webowych · Zweryfikowano Kwiecień 2026

1.  [Strona główna](/) ›
2.  [Baza wiedzy](/baza-wiedzy/) ›
3.  PostgreSQL performance tuning

# PostgreSQL performance tuning — shared\_buffers, EXPLAIN i pg\_stat\_statements

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS / Bazy danych

Domyślna konfiguracja PostgreSQL jest skrojona pod minimalny serwer z 128 MB RAM — `shared_buffers = 128MB`, `work_mem = 4MB`. Na serwerze produkcyjnym z 8-32 GB RAM te wartości to przepis na powolne zapytania i przeciążony dysk. Ten przewodnik pokazuje jak znaleźć wąskie gardła, dostosować parametry i zrozumieć plany zapytań.

## pg\_tune — kalkulator parametrów

Zamiast zgadywać wartości, użyj kalkulatora **pgtune.leopard.in.ua** — wpisujesz RAM, CPU, typ dysku (HDD/SSD/NVMe) i profil pracy (Web, OLTP, Data Warehouse, Desktop, Mixed) i dostajesz gotowy blok konfiguracyjny. Poniżej typowy output dla serwera 8 GB RAM / 4 CPU / SSD / profil Web:

\# /etc/postgresql/16/main/postgresql.conf
# pg\_tune output dla: 8 GB RAM, 4 CPU, SSD, profil Web

# Pamięć
shared\_buffers = 2GB              # 25% RAM
effective\_cache\_size = 6GB        # szacunek dostępnego OS cache (ok. 75% RAM)
work\_mem = 10MB                   # per-operacja (sort, hash join) — uważaj na wiele połączeń!
maintenance\_work\_mem = 512MB      # VACUUM, CREATE INDEX, ALTER TABLE

# Planer zapytań
random\_page\_cost = 1.1            # SSD: 1.1, HDD: 4.0 (domyślnie)
effective\_io\_concurrency = 200    # SSD: 200, HDD: 2
seq\_page\_cost = 1.0

# Checkpoint i WAL
wal\_buffers = 16MB
checkpoint\_completion\_target = 0.9
min\_wal\_size = 1GB
max\_wal\_size = 4GB

# Połączenia
max\_connections = 100             # dla pgBouncer zmniejsz do 20-30
shared\_preload\_libraries = 'pg\_stat\_statements'   # do profilowania

# Logowanie wolnych zapytań
log\_min\_duration\_statement = 1000    # loguj zapytania > 1s
log\_checkpoints = on
log\_connections = off
log\_lock\_waits = on

\# Zastosuj zmiany
sudo systemctl reload postgresql

# Sprawdź bieżące wartości
psql -U postgres -c "SHOW shared\_buffers;"
psql -U postgres -c "SHOW work\_mem;"
psql -U postgres -c "SELECT name, setting, unit FROM pg\_settings WHERE name IN ('shared\_buffers','work\_mem','effective\_cache\_size');"

## EXPLAIN ANALYZE — analiza planów zapytań

`EXPLAIN ANALYZE` wykonuje zapytanie i pokazuje rzeczywisty plan, czasy i liczby wierszy. Dodaj `BUFFERS` żeby zobaczyć trafienia cache bufora — kluczowe dla diagnostyki I/O:

\-- Podstawowe EXPLAIN
EXPLAIN SELECT \* FROM orders WHERE user\_id = 123;

-- EXPLAIN ANALYZE — rzeczywiste wykonanie (uwaga: modyfikuje dane!)
EXPLAIN ANALYZE SELECT \* FROM orders WHERE user\_id = 123;

-- Pełna diagnostyka: BUFFERS + FORMAT JSON (do pgExplain.dalibo.com)
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
  SELECT o.id, o.total, u.email
  FROM orders o
  JOIN users u ON u.id = o.user\_id
  WHERE o.created\_at > NOW() - INTERVAL '30 days'
  ORDER BY o.created\_at DESC
  LIMIT 100;

-- Interpretacja kluczowych węzłów:
-- Seq Scan  → pełny skan tabeli (brak indeksu lub zbyt mało selektywny)
-- Index Scan → skan przez indeks (dobry wynik)
-- Index Only Scan → dane wyłącznie z indeksu (najszybszy)
-- Hash Join, Merge Join → join dużych tabel (sprawdź work\_mem)
-- Nested Loop → join małych tabel (OK) lub duże tabele bez indeksu (problem)

-- Sprawdź "rows estimated vs actual":
-- Duże rozbieżności = nieaktualne statystyki → ANALYZE tabela
ANALYZE orders;

## pg\_stat\_statements — znajdź najwolniejsze zapytania

\-- Włącz rozszerzenie (wymagane shared\_preload\_libraries = 'pg\_stat\_statements')
CREATE EXTENSION IF NOT EXISTS pg\_stat\_statements;

-- Top 10 najwolniejszych zapytań (całkowity czas)
SELECT
  LEFT(query, 80) AS query\_snippet,
  calls,
  ROUND(total\_exec\_time::numeric, 2) AS total\_ms,
  ROUND((total\_exec\_time / calls)::numeric, 2) AS avg\_ms,
  rows
FROM pg\_stat\_statements
ORDER BY total\_exec\_time DESC
LIMIT 10;

-- Top 10 najczęściej wywoływanych
SELECT
  LEFT(query, 80) AS query\_snippet,
  calls,
  ROUND((total\_exec\_time / calls)::numeric, 2) AS avg\_ms
FROM pg\_stat\_statements
ORDER BY calls DESC
LIMIT 10;

-- Zapytania z największą ilością odczytów z dysku (shared\_blks\_read)
SELECT
  LEFT(query, 80) AS query\_snippet,
  shared\_blks\_hit,
  shared\_blks\_read,
  ROUND(100.0 \* shared\_blks\_hit / NULLIF(shared\_blks\_hit + shared\_blks\_read, 0), 2) AS hit\_ratio
FROM pg\_stat\_statements
WHERE shared\_blks\_hit + shared\_blks\_read > 0
ORDER BY shared\_blks\_read DESC
LIMIT 10;

-- Reset statystyk (po dostosowaniu konfiguracji)
SELECT pg\_stat\_statements\_reset();

## Indeksy — BRIN, GiST i partial

Poza standardowym B-tree PostgreSQL oferuje wyspecjalizowane typy indeksów. Poniższa tabela pomaga wybrać właściwy:

Typ indeksu

Kiedy używać

Zalety

Wady

B-tree (domyślny)

Większość kolumn, =, <, >, BETWEEN, LIKE 'abc%'

Wszechstronny, szybki

Duży rozmiar, overhead na INSERT

BRIN

Duże tabele z korelacją fizyczną (timestamp, serial)

Minimalny rozmiar (kilka MB), fast INSERT

Tylko przybliżone dopasowanie, powolny dla losowych zapytań

GiST

Dane geometryczne, full-text search, range types

PostGIS, tsvector, inet, tsrange

Wolniejszy od B-tree dla prostych typów

GIN

JSONB, arrays, full-text search (tsvector)

Szybkie wyszukiwanie w kolekcjach

Duży rozmiar, wolny UPDATE

Partial index

Podzbiór wierszy z warunkiem WHERE

Mały, szybki dla filtrowanych zapytań

Działa tylko gdy WHERE pasuje do definicji indeksu

Covering index (INCLUDE)

Index Only Scan dla wybranych kolumn

Eliminuje heap access, szybkie SELECT

Większy rozmiar niż zwykły indeks

\-- BRIN dla tabeli logów (miliony wierszy, chronologicznie wstawianych)
CREATE INDEX CONCURRENTLY idx\_logs\_created\_brin
  ON access\_logs USING BRIN (created\_at)
  WITH (pages\_per\_range = 128);

-- GiST dla PostGIS (geolokalizacja)
CREATE INDEX idx\_locations\_geom
  ON locations USING GIST (coordinates);

-- Partial index — tylko aktywne zamówienia (nie archiwum)
CREATE INDEX idx\_orders\_active\_user
  ON orders (user\_id, created\_at)
  WHERE status NOT IN ('completed', 'cancelled', 'refunded');

-- Covering index — Index Only Scan dla typowego zapytania
CREATE INDEX idx\_products\_covering
  ON products (category\_id, is\_active)
  INCLUDE (name, price, slug);

-- Sprawdź czy indeks jest używany
SELECT schemaname, tablename, indexname, idx\_scan, idx\_tup\_read
FROM pg\_stat\_user\_indexes
ORDER BY idx\_scan ASC;  -- indeksy z idx\_scan=0 to kandydaci do usunięcia

-- Znajdź brakujące indeksy (tabele z dużą liczbą seq\_scan)
SELECT schemaname, relname, seq\_scan, idx\_scan,
  n\_live\_tup,
  ROUND(seq\_scan::numeric / NULLIF(seq\_scan + idx\_scan, 0) \* 100, 1) AS seq\_scan\_pct
FROM pg\_stat\_user\_tables
WHERE seq\_scan > 100
ORDER BY seq\_scan DESC;

## Autovacuum — tuning dla szybkich tabel

\-- Globalne parametry autovacuum w postgresql.conf
autovacuum = on                           # nigdy nie wyłączaj!
autovacuum\_max\_workers = 3
autovacuum\_naptime = 1min                 # co ile sprawdzać tabele
autovacuum\_vacuum\_scale\_factor = 0.05     # uruchom VACUUM po 5% dead tuples (domyślnie 20%)
autovacuum\_analyze\_scale\_factor = 0.02    # uruchom ANALYZE po 2% zmian (domyślnie 10%)
autovacuum\_cost\_delay = 2ms               # SSD: 2ms (domyślnie 20ms)
autovacuum\_cost\_limit = 400               # SSD: 400 (domyślnie 200)

-- Per-tabela override dla tabeli z intensywnym ruchem
ALTER TABLE orders SET (
  autovacuum\_vacuum\_scale\_factor = 0.01,
  autovacuum\_analyze\_scale\_factor = 0.005,
  autovacuum\_vacuum\_cost\_delay = 0
);

-- Sprawdź stan autovacuum
SELECT relname, n\_dead\_tup, last\_vacuum, last\_autovacuum, last\_analyze
FROM pg\_stat\_user\_tables
ORDER BY n\_dead\_tup DESC
LIMIT 10;

-- Sprawdź bloat tabel (puchnięcie)
SELECT
  tablename,
  pg\_size\_pretty(pg\_total\_relation\_size(quote\_ident(tablename))) AS total\_size,
  pg\_size\_pretty(pg\_relation\_size(quote\_ident(tablename))) AS table\_size
FROM pg\_tables
WHERE schemaname = 'public'
ORDER BY pg\_total\_relation\_size(quote\_ident(tablename)) DESC;

## Najczęstsze pytania

Jaką wartość ustawić dla shared\_buffers w PostgreSQL? +

Zasada ogólna: shared\_buffers = 25% dostępnego RAM. Na serwerze z 8 GB RAM ustaw shared\_buffers = 2GB. To bufor danych w pamięci PostgreSQL — im więcej, tym mniej odczytów z dysku. Nie ustawiaj więcej niż 40% RAM, bo reszta systemu (OS cache, połączenia) też potrzebuje pamięci. Kalkulator pgtune.leopard.in.ua automatycznie dobiera wszystkie parametry do profilu pracy bazy (OLTP, Web, DW, Mixed).

Do czego służy pg\_stat\_statements w PostgreSQL? +

pg\_stat\_statements to rozszerzenie PostgreSQL które śledzi statystyki wykonania zapytań SQL: całkowity czas, liczba wywołań, średni czas, wiersze zwrócone. Po włączeniu możesz znaleźć najwolniejsze i najczęstsze zapytania przez: SELECT query, calls, total\_exec\_time/calls AS avg\_ms FROM pg\_stat\_statements ORDER BY total\_exec\_time DESC LIMIT 20. To podstawowe narzędzie do profilowania bazy produkcyjnej.

Kiedy używać indeksu BRIN zamiast B-tree w PostgreSQL? +

BRIN (Block Range INdex) jest idealny dla dużych tabel z kolumnami o naturalnej korelacji z fizyczną kolejnością wierszy — np. timestamp created\_at w tabeli logów, auto-increment id. BRIN zajmuje mniej miejsca niż B-tree (kilka MB vs setki MB dla milionów wierszy) i ma minimalny overhead przy INSERT. Używaj BRIN gdy: tabela ma &gt;10 milionów wierszy, wstawiasz dane chronologicznie i pytasz o zakresy dat. B-tree jest lepszy dla kolumn z losową kolejnością i selektywnych zapytań punktowych.

Co to jest autovacuum i dlaczego jest ważny? +

PostgreSQL używa MVCC (Multi-Version Concurrency Control) — każdy UPDATE zostawia stary wiersz (dead tuple) i tworzy nowy. Autovacuum to demon który usuwa dead tuples i aktualizuje statystyki planera. Bez autovacuum tabele puchną (table bloat), zapytania zwalniają bo planer ma nieaktualne statystyki i może nastąpić transaction ID wraparound (katastrofalne przejście bazy w read-only). Autovacuum nie trzeba wyłączać — jeśli jest zbyt agresywny, dostosuj autovacuum\_cost\_delay i autovacuum\_vacuum\_scale\_factor.

## Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.

Contabo

VPS z dużą ilością RAM — shared\_buffers = 25% RAM wymaga minimum 4 GB

VPS + RAM

[Aktywuj rabat →](/out/contabo)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/contabo)

Mikr.us

Tani VPS do nauki PostgreSQL tuning i testów pg\_stat\_statements

Dev/Test

[Aktywuj rabat →](/out/mikrus)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/mikrus)

CyberFolks

Hosting z bazą MySQL/PostgreSQL — sprawdź opcje zarządzanej bazy

Hosting

[Aktywuj rabat →](/out/cyberfolks)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/cyberfolks)

## Powiązane strony

-   [pgBouncer — connection pooling dla PostgreSQL](/baza-wiedzy/pgbouncer-postgresql-pooling)
-   [PostgreSQL Streaming Replication — primary i standby](/baza-wiedzy/postgresql-streaming-replication)
-   [MySQL Slow Query Log — diagnostyka wolnych zapytań](/baza-wiedzy/mysql-slow-query-log)
-   [MySQL Replication — konfiguracja replikacji](/baza-wiedzy/mysql-replication-master-slave)
-   [Wszystkie artykuły](/baza-wiedzy/)