Klasická otázka, kterou předkládá vývojář svému DBA nebo majitel firmy konzultantovi PostgreSQL, zní téměř vždy stejně: "Proč trvá dokončení požadavků v databázi tak dlouho?"
Tradiční sada důvodů:
- neefektivní algoritmus
když se rozhodnete PŘIPOJIT SE k několika CTE přes několik desítek tisíc záznamů - zastaralé statistiky
pokud je skutečná distribuce dat v tabulce již velmi odlišná od distribuce, kterou naposledy shromáždila ANALYZE - „zapojování“ zdrojů
a již není dostatek vyhrazeného výpočetního výkonu CPU, gigabajty paměti jsou neustále čerpány nebo disk nestíhá držet krok se všemi „žádostmi“ databáze - blokování z konkurenčních procesů
A pokud je docela obtížné zachytit a analyzovat blokování, pak pro všechno ostatní potřebujeme plán dotazů, který lze získat pomocí
Ale jak je uvedeno ve stejné dokumentaci,
„Porozumět plánu je umění a jeho zvládnutí vyžaduje určitou dávku zkušeností...“
Ale můžete se bez něj obejít, pokud použijete správný nástroj!
Jak obvykle vypadá plán dotazů? Něco takového:
Index Scan using pg_class_relname_nsp_index on pg_class (actual time=0.049..0.050 rows=1 loops=1)
Index Cond: (relname = $1)
Filter: (oid = $0)
Buffers: shared hit=4
InitPlan 1 (returns $0,$1)
-> Limit (actual time=0.019..0.020 rows=1 loops=1)
Buffers: shared hit=1
-> Seq Scan on pg_class pg_class_1 (actual time=0.015..0.015 rows=1 loops=1)
Filter: (relkind = 'r'::"char")
Rows Removed by Filter: 5
Buffers: shared hit=1
nebo jako toto:
"Append (cost=868.60..878.95 rows=2 width=233) (actual time=0.024..0.144 rows=2 loops=1)"
" Buffers: shared hit=3"
" CTE cl"
" -> Seq Scan on pg_class (cost=0.00..868.60 rows=9972 width=537) (actual time=0.016..0.042 rows=101 loops=1)"
" Buffers: shared hit=3"
" -> Limit (cost=0.00..0.10 rows=1 width=233) (actual time=0.023..0.024 rows=1 loops=1)"
" Buffers: shared hit=1"
" -> CTE Scan on cl (cost=0.00..997.20 rows=9972 width=233) (actual time=0.021..0.021 rows=1 loops=1)"
" Buffers: shared hit=1"
" -> Limit (cost=10.00..10.10 rows=1 width=233) (actual time=0.117..0.118 rows=1 loops=1)"
" Buffers: shared hit=2"
" -> CTE Scan on cl cl_1 (cost=0.00..997.20 rows=9972 width=233) (actual time=0.001..0.104 rows=101 loops=1)"
" Buffers: shared hit=2"
"Planning Time: 0.634 ms"
"Execution Time: 0.248 ms"
Ale čtení plánu v textu „z listu“ je velmi obtížné a nejasné:
- se zobrazí v uzlu součet podle zdrojů podstromu
to znamená, abyste pochopili, kolik času trvalo spuštění konkrétního uzlu nebo kolik přesně toto čtení z tabulky přineslo data z disku, musíte nějak odečíst jeden od druhého - je potřeba čas uzlu násobit smyčkami
ano, odečítání není nejsložitější operace, kterou je nutné provést „v hlavě“ – koneckonců doba provedení je uvedena jako průměr na jedno provedení uzlu a mohou jich být stovky - no a to všechno dohromady nám brání odpovědět na hlavní otázku – tak kdo "nejslabší článek"?
Když jsme se to vše pokusili vysvětlit několika stovkám našich vývojářů, uvědomili jsme si, že to zvenčí vypadá asi takto:
A to znamená, že potřebujeme...
Nástroj
V něm jsme se pokusili shromáždit všechny klíčové mechaniky, které pomáhají pochopit „kdo je vinen a co dělat“ podle plánu a požadavku. No a podělte se o část svých zkušeností s komunitou.
Seznamte se a použijte -
Viditelnost plánů
Je snadné pochopit plán, když vypadá takto?
Seq Scan on pg_class (actual time=0.009..1.304 rows=6609 loops=1)
Buffers: shared hit=263
Planning Time: 0.108 ms
Execution Time: 1.800 ms
Ne opravdu.
Ale takhle, ve zkrácené podoběkdyž jsou klíčové indikátory odděleny, je to mnohem jasnější:
Pokud je ale plán složitější, přijde na pomoc piechart rozložení času podle uzlů:
No, u těch nejobtížnějších možností spěchá na pomoc graf pokroku:
Existují například zcela netriviální situace, kdy plán může mít více než jeden skutečný kořen:
Strukturální vodítka
No, pokud je celá struktura plánu a jeho bolavá místa již rozvržena a viditelná, proč je nezvýraznit developerovi a nevysvětlit je „ruským jazykem“?
Již jsme shromáždili několik desítek takových šablon doporučení.
Profiler dotazů řádek po řádku
Pokud nyní překryjete původní dotaz do analyzovaného plánu, můžete vidět, kolik času bylo vynaloženo na každý jednotlivý příkaz - něco takového:
...nebo i takto:
Nahrazení parametrů do požadavku
Pokud jste k plánu „připojili“ nejen požadavek, ale i jeho parametry z řádku DETAIL protokolu, můžete jej dodatečně zkopírovat v jedné z možností:
- se substitucí hodnoty v dotazu
pro přímé provedení na vaší základně a další profilováníSELECT 'const', 'param'::text;
- se substitucí hodnoty přes PREPARE/EXECUTE
emulovat práci plánovače, kdy parametrickou část lze ignorovat - například při práci na rozdělených tabulkáchDEALLOCATE ALL; PREPARE q(text) AS SELECT 'const', $1::text; EXECUTE q('param'::text);
Archiv plánů
Vkládejte, analyzujte, sdílejte s kolegy! Plány zůstanou archivovány a můžete se k nim později vrátit:
Pokud ale nechcete, aby ostatní viděli váš plán, nezapomeňte zaškrtnout políčko „nepublikovat v archivu“.
V následujících článcích budu hovořit o obtížích a rozhodnutích, která vznikají při analýze plánu.
Zdroj: www.habr.com