Ohnivá válka tohoto týdne na seznamu výkonu pgsql se opět točí kolem skutečnosti, že PostgreSQL nemá tradiční nápovědní syntaxi dostupnou v jiných databázích. Existuje směs technických a pragmatických důvodů, proč tomu tak je:
- Zavedení nápověd je častým zdrojem pozdějších problémů, protože opravit místo dotazu jednou ve zvláštním případě není příliš robustní přístup. Jak vaše datová sada roste a možná se mění i distribuce, nápad, který jste naznačili, když byl malý, se může stát čím dál horším nápadem.
- Přidání užitečného rozhraní nápovědy by zkomplikovalo kód optimalizátoru, jehož údržba tak, jak je, je dost náročná. Jedním z důvodů, proč PostgreSQL funguje stejně dobře jako spouštění dotazů, je dobrý pocit z kódu („můžeme zaškrtnout nápovědu na našem seznamu funkcí pro porovnání dodavatelů!“), který se ve skutečnosti nevyplácí, pokud jde o vytváření databáze. dostatečně lépe ospravedlnit jeho pokračující údržbu, je odmítnuto politikou. Pokud to nefunguje, nebude přidáno. A při objektivním hodnocení jsou rady v průměru spíše problémem než řešením.
- Ten druh problémů, které rady fungují, mohou být chyby optimalizátoru. Komunita PostgreSQL reaguje na skutečné chyby v optimalizátoru rychleji než kdokoli jiný v oboru. Zeptejte se a nemusíte se setkávat s mnoha uživateli PostgreSQL, než najdete někoho, kdo nahlásil chybu a sledoval, jak se do druhého dne opraví.
Nyní, hlavní zcela platná odpověď na zjištění, že chybí rady, obvykle od správců databází, kteří jsou na ně zvyklí, je:„Jak si poradím s chybou optimalizátoru, když na ni narazím?“ Stejně jako všechny technické práce v dnešní době je obvykle vyvíjen obrovský tlak na získání co nejrychlejší opravy, když se objeví problém se špatným dotazem.
Pokud by PostgreSQL neměl nějaké způsoby, jak tuto situaci řešit, neexistovaly by žádné seriózní produkční databáze PostgreSQL . Rozdíl je v tom, že věci, které upravujete v této databázi, jsou více zakořeněny v ovlivňování rozhodnutí, která již optimalizátor činí, a to poměrně nenápadným způsobem, než abyste mu pouze říkali, co má dělat. Toto jsou rady v doslovném slova smyslu, jen nemají nějaké uživatelské rozhraní, které by naznačovalo, že uživatelé jiných databází, kteří jsou noví v PostgreSQL, hledají.
S ohledem na to se pojďme podívat na to, co v PostgreSQL můžete obejít špatné plány dotazů a chyby optimalizátoru, zejména věci, o kterých si mnoho lidí myslí, že lze vyřešit pouze pomocí:
- join_collapse_limit: Toto upravuje, jak velkou flexibilitu má optimalizátor při změně pořadí spojení více tabulek. Normálně zkouší všechny možné kombinace, když lze spojení přeskupit (což je většinou, pokud nepoužíváte vnější spojení). Snížení join_collapse_limit, možná dokonce na 1, odstraní část nebo veškerou tuto flexibilitu. Když je nastaveno na 1, získáte spojení v pořadí, ve kterém jste je napsali, tečka. Plánování velkého počtu spojení je jednou z nejtěžších věcí, kterou musí optimalizátor udělat; každé spojení zvětšuje chyby v odhadech a prodlužuje dobu plánování dotazů. Pokud je ze základní povahy vašich dat zřejmé, k jakému spojení by mělo dojít, a neočekáváte, že se to někdy změní, jakmile zjistíte správné pořadí, můžete je uzamknout pomocí tohoto parametru.
- random_page_cost: Výchozí hodnota je 4,0 a tento parametr nastavuje, jak drahé je hledání na disku za účelem nalezení náhodné stránky na disku, vzhledem k referenční hodnotě 1,0. Ve skutečnosti, když změříte poměr náhodného a sekvenčního I/O na běžných pevných discích, zjistíte, že toto číslo se blíží 50. Proč tedy 4.0? Za prvé, protože to fungovalo lépe než větší hodnoty v komunitním testování. Za druhé, v mnoha případech budou zejména data indexu uložena do mezipaměti, což sníží efektivní náklady na čtení těchto hodnot. Pokud je například váš index z 90 % uložen v paměti RAM, znamená to, že v 10 % případů budete provádět operaci, která je 50krát dražší; to by činilo vaše efektivní random_page_cost asi 5. Tento druh situace v reálném světě je důvodem, proč výchozí hodnota dává smysl tam, kde je. Normálně vidím, že oblíbené indexy získávají více než 95 % mezipaměti v paměti. Pokud je ve skutečnosti mnohem pravděpodobnější, že váš index bude v paměti RAM, snížení random_page_cost až na hodnotu těsně nad 1,0 může být rozumnou volbou, aby bylo zřejmé, že není dražší než jakékoli jiné čtení. Náhodné vyhledávání na skutečně vytížených systémech může být zároveň mnohem dražší, než byste očekávali od pouhého prohlížení simulací pro jednoho uživatele. Musel jsem nastavit random_page_cost až na 60, aby databáze přestala používat indexy, když plánovač špatně odhadoval, jak drahé budou. Tato situace obvykle pochází z chyby odhadu citlivosti na straně plánovače – pokud skenujete více než 20 % tabulky, plánovač ví, že použití sekvenčního skenování bude mnohem efektivnější než indexové skenování. Ošklivá situace, kdy jsem musel k takovému chování přinutit mnohem dříve, nastala, když plánovač očekával vrácení 1 % řádků, ale ve skutečnosti se to blížilo 15 %.
- work_mem: Upravuje, kolik paměti je k dispozici pro dotazy provádějící třídění, hašování a podobné operace založené na paměti. Toto je pouze hrubé vodítko pro dotazy, nikoli pevný limit, a jeden klient může skončit pomocí násobků work_mem při spuštění dotazu. V souladu s tím musíte být opatrní, abyste tuto hodnotu nenastavili v souboru postgresql.conf příliš vysoko. Co však můžete udělat místo toho, nastaví to před spuštěním dotazu, který skutečně těží z toho, že má extra paměť pro uložení dat pro třídění nebo hash. Někdy můžete najít tyto dotazy z protokolování pomalých pomocí log_min_duration_statement. Můžete je také najít zapnutím log_temp_files, které se zaprotokolují pokaždé, když je work_mem příliš malý, a proto se operace řazení rozsypou na disk, místo aby se odehrávaly v paměti.
- OFFSET 0: PostgreSQL přeuspořádá poddotazy do formy spojení, takže pak může k optimalizaci použít běžnou logiku pořadí spojení. V některých případech může být toto rozhodnutí opravdu špatné, protože věci, které lidé mají tendenci psát jako dílčí dotazy, se z nějakého důvodu zdají trochu obtížnější odhadnout (to říkám na základě počtu takových obtížných dotazů, které vidím). Jeden záludný trik, který můžete udělat, abyste této logice zabránili, je umístit OFFSET 0 na konec dílčího dotazu. Výsledky to nijak nemění, ale vložení typu uzlu dotazu Limit použitého k provedení OFFSET zabrání přeuspořádání. Poddotaz se pak vždy spustí tak, jak to většina lidí očekává – jako vlastní izolovaný uzel dotazu.
- enable_seqscan, enable_indexscan, enable_bitmapscan: Vypnutí jedné z těchto funkcí pro vyhledávání řádků v tabulce je poměrně velké kladivo, které důrazně doporučuje vyhnout se tomuto typu skenování (ne vždy mu zabránit – pokud neexistuje způsob, jak provést svůj plán, ale seqscan, získáte seqscan, i když jsou parametry vypnuté). Hlavní věc, pro kterou je doporučuji, není opravovat dotazy, ale vyzkoušet si EXPLAIN a zjistit, proč byl preferován jiný typ skenování.
- enable_nestloop, enable_hashjoin, enable_mergejoin: Pokud se domníváte, že vaším problémem je typ použitého spojení, nikoli způsob čtení tabulek, zkuste pomocí jednoho z těchto parametrů vypnout typ, který vidíte ve svém plánu, a poté spusťte EXPLAIN znovu. Chyby v odhadech citlivosti mohou snadno způsobit, že spojení bude vypadat více či méně efektivně, než ve skutečnosti je. A znovu, vidět, jak se plán mění se současnou deaktivovanou metodou spojení, může být velmi informativní, proč se rozhodl pro tuto metodu.
- enable_hashagg, enable_material: Tyto funkce jsou v PostgreSQL relativně nové. Agresivní používání Hash Aggregation bylo představeno ve verzi 8.4 a agresivnější materializace ve verzi 9.0. Pokud vidíte tyto typy uzlů ve výstupu EXPLAIN
a zdá se, že dělají něco špatně, protože tento kód je mnohem novější, je o něco pravděpodobnější, že bude mít omezení nebo chybu než některé starší funkce. Pokud jste měli plán, který fungoval dobře ve starších verzích PostgreSQL, ale používá jeden z těchto typů uzlů a zdá se, že v důsledku toho funguje mnohem hůře, deaktivace těchto funkcí vás může někdy vrátit k dřívějšímu chování – a také trochu posvítit proč optimalizátor udělal špatnou věc jako užitečnou zpětnou vazbu. Všimněte si, že toto je obecně způsob, jakým se do PostgreSQL obvykle zavádějí pokročilejší funkce: s možností vypnutí pro účely odstraňování problémů, pokud se ukáže, že došlo k regresi plánu vzhledem k tomu, jak věci prováděly dřívější verze. - cursor_tuple_fraction: Pokud nechcete číst všechny řádky z dotazu zpět, měli byste to implementovat pomocí kurzoru. V takovém případě se optimalizátor snaží upřednostnit, zda vám rychle vrátí první řádek, nebo zda raději optimalizuje celý dotaz na základě tohoto parametru. Ve výchozím nastavení databáze předpokládá, že při použití kurzoru znovu přečtete 10 % dotazu. Úprava tohoto parametru vám umožní uchýlit jej k očekávání, že budete číst méně nebo více.
Všechny tyto parametry a vylepšení dotazů by měly být považovány za úpravy třídění. Nechcete s nimi běžet věčně (snad kromě join_collapse_limit). Používáte je, abyste se dostali ze zácpy, a pak doufejme, že zjistíte, co je skutečnou základní příčinou špatného plánu – špatné statistiky, omezení/chyba optimalizátoru nebo něco jiného – a pak problém z tohoto směru vyřešíte. Čím více posouváte chování optimalizátoru určitým směrem, tím více jste vystaveni budoucím změnám ve vašich datech, které již netlačí správné. Pokud je používáte správně, jako způsob, jak studovat, proč jste dostali špatný plán (přístup, který jsem použil v kapitole optimalizace dotazů v PostgreSQL 9.0 High Performance), způsob, jakým naznačujete věci v PostgreSQL, by měl vést k tomu, že opustíte každý běh- se špatným chováním optimalizátoru trochu chytřejší o tom, jak se v budoucnu vyhnout této třídě problémů