Část, kterou jsem vždy považoval za matoucí, jsou počáteční náklady vs. celkové náklady. Google to pokaždé, když na to zapomenu, což mě přivádí zpět sem, což nevysvětluje rozdíl, a proto píšu tuto odpověď. To je to, co jsem získal z Postgres EXPLAIN
dokumentaci, vysvětlenou, jak tomu rozumím.
Zde je příklad z aplikace, která spravuje fórum:
EXPLAIN SELECT * FROM post LIMIT 50;
Limit (cost=0.00..3.39 rows=50 width=422)
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Zde je grafické vysvětlení z PgAdmin:
(Když používáte PgAdmin, můžete umístit kurzor myši na komponentu a přečíst si podrobnosti o ceně.)
Náklady jsou uvedeny jako n-tice, např. náklady na LIMIT
je cost=0.00..3.39
a náklady na sekvenční skenování post
je cost=0.00..15629.12
. První číslo v n-tici je náklady na spuštění a druhé číslo je celkové náklady . Protože jsem použil EXPLAIN
a ne EXPLAIN ANALYZE
, tyto náklady jsou odhady, nikoli skutečná opatření.
- Počáteční náklady je ošemetný koncept. Nepředstavuje pouze dobu, než se daná komponenta spustí . Představuje množství času mezi tím, kdy se komponenta začne vykonávat (načítání dat) a kdy komponenta vydá svůj první řádek .
- Celkové náklady je celá doba provádění komponenty, od okamžiku, kdy začne načítat data, až po dokončení zápisu výstupu.
Komplikací je, že náklady každého „nadřazeného“ uzlu zahrnují náklady jeho podřízených uzlů. V textové reprezentaci je strom reprezentován odsazením, např. LIMIT
je nadřazený uzel a Seq Scan
je jeho dítětem. V reprezentaci PgAdmin šipky ukazují z potomka na rodiče – směr toku dat – což může být kontraintuitivní, pokud znáte teorii grafů.
Dokumentace říká, že náklady zahrnují všechny podřízené uzly, ale všimněte si, že celkové náklady nadřazeného uzlu 3.39
je mnohem nižší než celkové náklady na jeho potomka 15629.12
. Celkové náklady nezahrnují, protože se jedná o součást jako LIMIT
nemusí zpracovávat celý svůj vstup. Viz EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
příklad v Postgres EXPLAIN
dokumentaci.
Ve výše uvedeném příkladu je čas spuštění nula pro obě komponenty, protože ani jedna komponenta nemusí provést žádné zpracování, než začne zapisovat řádky:sekvenční skenování přečte první řádek tabulky a vydá jej. LIMIT
přečte svůj první řádek a poté jej vydá.
Kdy by komponenta potřebovala provést velké množství zpracování, než by mohla začít vydávat nějaké řádky? Možných důvodů je spousta, ale podívejme se na jeden jasný příklad. Zde je stejný dotaz jako dříve, ale nyní obsahuje ORDER BY
klauzule:
EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50;
Limit (cost=23283.24..23283.37 rows=50 width=422)
-> Sort (cost=23283.24..23859.27 rows=230412 width=422)
Sort Key: body
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
A graficky:
Ještě jednou, sekvenční skenování na post
nemá žádné spouštěcí náklady:okamžitě začne vydávat řádky. Toto řazení má však značné počáteční náklady 23283.24
protože musí seřadit celou tabulku, než bude moci vytisknout byť jen jeden řádek . Celkové náklady na řazení 23859.27
je jen o málo vyšší než spouštěcí náklady, což odráží skutečnost, že jakmile je celá datová sada roztříděna, mohou být roztříděná data vydána velmi rychle.
Všimněte si, že čas spuštění LIMIT
23283.24
se přesně rovná době spuštění tohoto druhu. Není tomu tak proto, že LIMIT
sám má vysokou dobu spuštění. Ve skutečnosti má sám o sobě nulovou dobu spuštění, ale EXPLAIN
shrne všechny podřízené náklady pro každého rodiče, takže LIMIT
čas spuštění zahrnuje součet časů spuštění jeho potomků.
Tento souhrn nákladů může ztížit pochopení nákladů na provedení každé jednotlivé součásti. Například naše LIMIT
má nulovou dobu spuštění, ale to není na první pohled patrné. Z tohoto důvodu se několik dalších lidí připojilo na stránku explain.depesz.com, nástroj vytvořený Hubertem Lubaczewskim (aka depesz), který pomáhá pochopit EXPLAIN
— mimo jiné — odečtením nákladů na dítě od nákladů rodičů. V krátkém blogovém příspěvku o svém nástroji zmiňuje některé další složitosti.