Zpět do magazínu
Databáze07.04.2026
Databáze

N+1 a další výkonnostní pasti, do kterých spadne každý začátečník.

Aplikace běhala svižně, a najednou se vleče. Většinou za to může N+1 problém nebo chybějící index. Ukážu ti, jak je odhalit a opravit.

PB
Petr Bláha
Autor
7. duben 2026
Publikováno
8 min
Čtení

Skoro každá aplikace začne pomalu škobrtat ve chvíli, kdy přibydou data a uživatelé. Ve většině případů za to nemůže slabý server, ale pár klasických chyb v práci s databází. Tu nejčastější, N+1 problém, vidím pořád dokola u začátečníků i ostřílených vývojářů. Ukážu ti, jak ji poznat a opravit, a přihodím další pasti, kterým se vyhnout.

N+1 problém: tichý zabiják výkonu

N+1 vzniká, když místo jednoho chytrého dotazu pošleš do databáze jeden dotaz na seznam a pak ke každé položce další dotaz zvlášť. Při deseti položkách je to jedenáct dotazů, při tisíci tisíc a jeden. Databáze sice každý dotaz obslouží rychle, ale když jich pošleš tisíce, nasčítá se z toho zpoždění, které uživatel jasně cítí.

-- 1 dotaz: vytáhni objednávky
SELECT id, user_id FROM orders;

-- a pak pro KAŽDOU objednávku zvlášť (to je to N)
SELECT name FROM users WHERE id = '...';
SELECT name FROM users WHERE id = '...';
-- ... a tak pořád dokola

Řešení je spojit data jedním dotazem přes JOIN. Místo tisíce dotazů pošleš jeden a databáze ti vrátí všechno najednou, navíc mnohem efektivněji, protože si práci sama optimalizuje.

-- jeden dotaz místo tisíce
SELECT orders.id, users.name
FROM orders
JOIN users ON users.id = orders.user_id;

Zákeřné na N+1 je, že ho při vývoji nevidíš. S deseti testovacími řádky aplikace lítá a všechno se zdá v pořádku. Problém vyleze až na produkci s tisíci řádky, často v nejhorší možnou chvíli, třeba při náporu o víkendu.

Chybějící indexy: hledání jehly bez magnetu

Když databáze hledá řádek podle sloupce bez indexu, projíždí celou tabulku řádek po řádku. U tisíce řádků to nepoznáš, u milionu se to vleče vteřiny. Index je jako rejstřík v knize: místo listování celou knihou skočíš rovnou na správnou stránku. Je to možná nejúčinnější jednotlivá optimalizace, kterou můžeš udělat.

-- pokud podle user_id často filtruješ, dej na něj index
CREATE INDEX IF NOT EXISTS idx_orders_user
  ON orders(user_id);

Index dej na sloupce, podle kterých často filtruješ (WHERE), spojuješ (JOIN) nebo řadíš (ORDER BY). Typicky jsou to cizí klíče a sloupce jako user_id, tenant_id, status nebo created_at. Naopak na sloupce, podle kterých nikdy nehledáš, index dávat nemusíš.

Další pasti, na které narazíš

N+1 a chybějící indexy jsou největší žrouti výkonu, ale nejsou jediní. Tady je přehled dalších chyb, které vidím nejčastěji a které jdou většinou opravit jednoduchou úpravou dotazu.

PastCo to děláŘešení
SELECT *Tahá zbytečně všechny sloupce včetně velkýchVyjmenuj jen sloupce, které potřebuješ
Žádné stránkováníNačte všech 50 000 řádků narázLIMIT a OFFSET nebo kurzorové stránkování
Filtrování v kóduStáhne vše a třídí až v aplikaciFiltruj přes WHERE v databázi
Počítání v kóduStáhne řádky jen aby je spočítaloPoužij COUNT přímo v dotazu

Stránkuj, nikdy netahej všechno

Klasická chyba je načíst do přehledu úplně všechny záznamy. Při pár desítkách to nevadí, ale jakmile jich máš desetitisíce, zabiješ tím databázi i prohlížeč, který musí všechno vykreslit. Vždy načítej po stránkách, uživatel stejně víc než jednu obrazovku najednou nepřečte.

-- druhá stránka po 20 položkách
SELECT id, title, created_at FROM articles
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;

Jak poznat, co aplikaci brzdí

Hádání je špatná strategie, protože člověk skoro vždy hádá špatně. Databáze ti naštěstí sama řekne, jak dotaz provádí, stačí se zeptat příkazem EXPLAIN. Pokud v jeho výstupu uvidíš sekvenční scan přes velkou tabulku, většinou tam chybí index.

-- ukáže, jak databáze dotaz provede
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = '...';

Postup, kterým výkonnostní problémy řeším, je vždy stejný. Nejdřív měřit, pak až opravovat, nikdy obráceně.

  1. Najdi pomalý dotaz. Většinou víš, která stránka se vleče.
  2. Spusť na něj EXPLAIN ANALYZE. Zjisti, kde dotaz tráví čas.
  3. Oprav příčinu. Přidej index, sluč N+1 do JOINu, zaveď stránkování.
  4. Změř znovu. Ověř, že to opravdu pomohlo, ne jen že ti to tak připadá.

Mentální test na deset tisíc uživatelů

Vždycky když píšu dotaz, ptám se sám sebe: co tenhle kód udělá při deseti tisících řádcích? Když odpověď zní bude posílat deset tisíc dotazů nebo natáhne všechno do paměti, vím, že to musím přepsat hned, ne až to spadne. Tahle jednoduchá otázka mi ušetřila spoustu nočních zásahů a stresu z padajících serverů.

Hodně z těchhle problémů jde předejít už při návrhu, když máš rozumné databázové schéma a indexy promyšlené dopředu. Oprava na produkci je vždycky dražší a nervóznější než dobrý začátek.

Cachuj to, co se nemění každou vteřinu

Někdy nejrychlejší dotaz je ten, který vůbec nemusíš poslat. Pokud čteš pořád dokola stejná data, která se mění jen občas, vyplatí se výsledek dočasně uložit do cache. Místo zatěžování databáze pak odpovídáš z paměti, což je řádově rychlejší a databázi to odlehčí.

Nepřežeň to ale hned na začátku. Cache přidává složitost, protože musíš řešit, kdy uložená data zneplatí. Sáhni po ní až ve chvíli, kdy konkrétní dotaz reálně brzdí, ne preventivně všude. Předčasná optimalizace je stejná past jako žádná optimalizace, jen z druhé strany, a stojí tě zbytečný čas i přehlednost kódu.

Časté dotazy

Mám dát index na úplně všechno?

Ne. Každý index zrychluje čtení, ale zpomaluje zápis a zabírá místo, protože se musí při každé změně dat aktualizovat. Indexuj sloupce, podle kterých reálně filtruješ, spojuješ a řadíš, ne všechno na slepo.

Jak poznám N+1 problém, když ho nevidím?

Sleduj počet dotazů na jedno načtení stránky. Když roste s počtem položek v seznamu, máš N+1. Pomůže ti k tomu logování dotazů nebo nástroj na monitoring databáze, který ti počet dotazů ukáže přehledně.

Je JOIN vždycky lepší než víc dotazů?

Skoro vždy, pokud nahrazuje N+1. Jeden JOIN je výrazně rychlejší než tisíc malých dotazů. Výjimkou jsou extrémně složité JOINy přes mnoho tabulek, kde někdy pomůže rozdělit to chytře jinak, ale to je už pokročilá optimalizace, kterou na začátku neřeš.

Pokud se ti aplikace vleče a nevíš, kde začít hledat, umím to obvykle najít rychle, protože tyhle pasti znám nazpaměť. Ozvi se mi přes kontakt a pojďme tvou aplikaci zrychlit.