query = kveryTakéExecution plan, Prováděcí plán, Plán vykonání dotazu, EXPLAIN plánPokročilý

Definice

Dotazovací plán je postup kroků, kterým databáze skutečně vykoná zadaný SQL dotaz: v jakém pořadí čte tabulky, zda použije index nebo sekvenční průchod a jakou metodou spojí data. Plán sestavuje plánovač na základě statistik o datech a vybírá z více možností tu s nejnižší odhadovanou cenou.

Kategorie: DatabázeAktualizováno

Od deklarativního dotazu k postupu vykonání

SQL popisuje, jaká data chce uživatel dostat, nikoli jak je získat. Mezi zápisem dotazu a výsledkem proto stojí plánovač (optimalizátor), který dotaz přepíše do stromu fyzických operací: skenů, filtrů, spojení, řazení a agregací. Tenhle strom je dotazovací plán a právě on rozhoduje, zda odpověď přijde za dvě milisekundy nebo za dvě minuty.

Jak plánovač vybírá jednu variantu z mnoha

Plánovač pro jeden dotaz uvažuje desítky až tisíce ekvivalentních variant. Ke každé přiřadí odhad ceny, tedy abstraktní číslo složené z počtu čtených stránek a procesorové práce. Vstupem odhadu jsou statistiky: počet řádků tabulky, histogramy hodnot sloupců, podíl distinktních hodnot a korelace uložení. Z těch plánovač odvodí selektivitu podmínky, tedy kolik řádků podmínka pravděpodobně propustí dál.

Typické uzly plánu

  • Seq Scan / Full Table Scan: průchod celou tabulkou, výhodný, když dotaz stejně vrací většinu řádků.
  • Index Scan: čtení přes index a doskok do tabulky, výhodné u vysoce selektivních podmínek.
  • Nested Loop: spojení vhodné, když je vnější strana malá a vnitřní má index.
  • Hash Join: postaví hashovací tabulku z menší strany, dobrý pro velká spojení bez pořadí.
  • Merge Join: spojení nad setříděnými vstupy, typicky po indexu nebo po řazení.

Odhad versus skutečnost

Plán obsahuje odhadované počty řádků. Když se odhad rozejde se skutečností o řády, plánovač zvolí špatnou strategii: nested loop nad milionem řádků místo hash joinu. Nejčastější příčiny jsou zastaralé statistiky, korelované sloupce (město a PSČ), funkce nad sloupcem v podmínce a parametry, jejichž hodnotu plánovač při přípravě neznal.

Čtení plánu v praxi

Většina databází nabízí příkaz nebo nástroj, který plán vypíše. V PostgreSQL a MySQL je to EXPLAIN, s variantou EXPLAIN ANALYZE, která dotaz opravdu spustí a přidá naměřené časy a skutečné počty řádků. Plán se čte odspodu nahoru a zevnitř ven: nejhlouběji odsazené uzly běží první a předávají řádky rodiči.

Při ladění se vyplatí sledovat tři věci: uzel s největším podílem času, největší rozdíl mezi rows odhadovaným a skutečným, a přítomnost operací, které tečou na disk (řazení nebo hash, které se nevejdou do paměti).

Cache plánů a proč se plán mění

Sestavení plánu stojí čas, proto systémy plány cachují pro opakované dotazy s parametry. To přináší riziko: plán zvolený pro jednu hodnotu parametru může být katastrofální pro jinou (parameter sniffing). Plán se také legitimně mění po přepočtu statistik, po růstu tabulky nebo po přidání indexu, takže stejný SQL dotaz může být v pondělí rychlý a ve středu pomalý, aniž by kdokoli sáhl do kódu.

Kdy plán nezachrání ani optimalizátor

Optimalizátor pracuje jen s tím, co dostane. Dotaz vracející statisíce řádků do aplikace, chybějící index na spojovacím klíči nebo schéma vynucující spojení přes pět tabulek na každý řádek jsou problémy návrhu, ne plánování. Plán je v takové situaci užitečný hlavně jako diagnóza, která ukáže, kde náklad skutečně vzniká.

Příklady z praxe

  1. EXPLAIN ANALYZE odhalí chybějící index

    Výpis objednávek zákazníka trvá na produkci přes sekundu, i když tabulka má jen pár milionů řádků. Plán ukáže sekvenční průchod celou tabulkou a filtr, který zahodí 99,9 % řádků. Po vytvoření indexu nad sloupcem customer_id plánovač zvolí Index Scan a dotaz spadne na jednotky milisekund.

    EXPLAIN ANALYZE
    SELECT id, total, created_at
    FROM orders
    WHERE customer_id = 4211
    ORDER BY created_at DESC
    LIMIT 20;
    
    -- před indexem:
    -- Seq Scan on orders  (cost=0.00..48213.00 rows=1 width=24)
    --   (actual time=0.4..980.2 rows=37 loops=1)
    --   Filter: (customer_id = 4211)
    --   Rows Removed by Filter: 2999963
  2. Špatný odhad kvůli funkci nad sloupcem

    Report filtruje faktury podle roku zápisem YEAR(issued_at) = 2024. Plánovač na výraz nemá statistiky ani index, takže odhadne pevný podíl řádků a zvolí nested loop nad velkou tabulkou. Přepsání podmínky na rozsah dat umožní použít index a odhad se přiblíží skutečnosti.

    -- plánovač neumí použít index nad issued_at
    WHERE YEAR(issued_at) = 2024
    
    -- rozsahová podoba, index je použitelný
    WHERE issued_at >= '2024-01-01'
      AND issued_at <  '2025-01-01'

Časté omyly

MýtusKdyž je na sloupci index, databáze ho vždycky použije.
Ve skutečnostiPlánovač index použije jen tehdy, když mu podle odhadu vyjde levněji než sekvenční průchod. Při dotazu vracejícím velkou část tabulky je čtení celé tabulky rychlejší, protože se vyhne náhodným skokům do datových stránek.
MýtusEXPLAIN mi ukáže, jak dlouho dotaz poběží.
Ve skutečnostiSamotný EXPLAIN vrací jen odhady optimalizátoru v abstraktních jednotkách ceny, nikoli čas. Skutečné časy a počty řádků dá až EXPLAIN ANALYZE, který dotaz opravdu vykoná (u zápisů proto raději v transakci s rollbackem).
MýtusKdyž dotaz zpomalil, musel se změnit kód aplikace.
Ve skutečnostiPlán se mění i bez zásahu do kódu: po přepočtu statistik, růstu dat, změně parametrů dotazu nebo přidání indexu jinde. Stejný SQL text tak může dostat jiný plán a řádově jiný čas.

Časté dotazy

Jak se čte výstup EXPLAIN v PostgreSQL?
Výstup EXPLAIN se čte odspodu nahoru a od nejhlubšího odsazení k nejmenšímu. Nejvíce odsazené uzly se vykonají první a předávají řádky nadřazenému uzlu. U každého uzlu jsou zajímavá čísla cost (odhad ceny startu a celku), rows (odhadovaný počet řádků) a width. S přepínačem ANALYZE přibude actual time a actual rows, takže lze porovnat odhad se skutečností. Ladění začíná u uzlu, který spotřebuje nejvíc času, a u uzlu s největším nepoměrem mezi odhadovanými a skutečnými řádky.
Proč databáze u stejného dotazu občas zvolí jiný dotazovací plán?
Dotazovací plán vzniká znovu na základě aktuálních statistik, takže se mění s daty. Po hromadném importu, po automatickém přepočtu statistik, po přidání či zrušení indexu nebo při jiné hodnotě parametru může optimalizátor vyhodnotit jako nejlevnější jinou variantu. Roli hraje i konfigurace paměti a nastavení nákladových konstant. Změna plánu je běžná a většinou žádoucí, problém nastává, když se odhady rozejdou se skutečností a nová varianta je výrazně pomalejší.
Co dělat, když se odhad počtu řádků v plánu výrazně liší od skutečnosti?
Prvním krokem je přepočet statistik (ANALYZE, respektive ekvivalentní příkaz dané databáze) a kontrola, zda autovacuum nebo obdobný proces vůbec běží. Dále pomáhá odstranit funkce a přetypování nad filtrovanými sloupci, protože nad výrazem databáze statistiky nemá. U korelovaných sloupců lze v PostgreSQL vytvořit rozšířené statistiky. Pokud problém způsobuje uložený plán pro jinou hodnotu parametru, řeší se přeplánováním dotazu nebo rozdělením na varianty podle typického rozsahu hodnot.
Lze dotazovací plán vynutit ručně?
Vynucení plánu je možné, ale považuje se za poslední možnost. Oracle a SQL Server nabízejí hinty přímo v dotazu a mechanismy pro uložení plánu, MySQL má indexové hinty, PostgreSQL hinty záměrně nemá a nabízí jen globální přepínače typu enable_nestloop pro diagnostiku. Vynucený plán zamrzne rozhodnutí, které platilo pro tehdejší objem dat, a po jejich růstu se často stane brzdou. Lepší je nejdřív opravit příčinu: statistiky, zápis podmínek nebo chybějící index.

Zdroje

  1. Using EXPLAIN(otevře se v novém okně)PostgreSQL Global Development Group
  2. Query Planning(otevře se v novém okně)PostgreSQL Global Development Group
  3. Query Planning(otevře se v novém okně)SQLite
  4. Explain Results(otevře se v novém okně)MongoDB
  5. Query processing architecture guide(otevře se v novém okně)Microsoft Learn

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.