Veckans flammakrig på pgsql-prestandalistan kretsar återigen kring det faktum att PostgreSQL inte har den traditionella ledtrådssyntaxen tillgänglig i andra databaser. Det finns en blandning av tekniska och pragmatiska skäl bakom varför det är:
- Introduktion av tips är en vanlig källa till senare problem, eftersom att fixa en frågeplats en gång i ett specialfall inte är ett särskilt robust tillvägagångssätt. När din datamängd växer, och eventuellt även ändrar distribution, kan idén du antydde när den var liten bli en allt dåligare idé.
- Att lägga till ett användbart tipsgränssnitt skulle komplicera optimeringskoden, som är svår nog att underhålla som den är. En del av anledningen till att PostgreSQL fungerar lika bra som det gör att köra frågor är att feel-good-kod ("vi kan bocka av antydningar på vår lista över leverantörsjämförelser!") som faktiskt inte betalar för sig, när det gäller att göra databasen bättre nog att motivera dess fortsatta underhåll, avvisas av policyn. Om det inte fungerar läggs det inte till. Och när de utvärderas objektivt är tips i genomsnitt ett problem snarare än en lösning.
- Den typ av problem som tyder på att det fungerar kan vara optimeringsbuggar. PostgreSQL-communityt reagerar på sanna buggar i optimeraren snabbare än någon annan i branschen. Fråga runt och du behöver inte träffa många PostgreSQL-användare innan du hittar en som har rapporterat ett fel och sett det fixas till nästa dag.
Nu är det huvudsakliga fullständigt giltiga svaret på att ta reda på att tips saknas, normalt från DBA:er som är vana vid dem, "ja, hur hanterar jag en optimeringsbugg när jag stöter på den?" Som allt tekniskt arbete nuförtiden, är det vanligtvis ett enormt tryck för att få den snabbaste möjliga lösningen när ett dåligt frågeproblem dyker upp.
Om PostgreSQL inte hade några sätt att hantera den situationen skulle det inte finnas några seriösa PostgreSQL-produktionsdatabaser . Skillnaden är att de saker du justerar i den här databasen är mer förankrade i att påverka de beslut som optimeraren redan fattar på ett ganska subtilt sätt, snarare än att du bara talar om för den vad den ska göra. Det här är ledtrådar i ordets bokstavliga bemärkelse, de har helt enkelt inte ett användargränssnitt för att antyda att användare av andra databaser nya för PostgreSQL letar efter.
Med det i åtanke, låt oss ta en titt på vad du kan göra i PostgreSQL för att komma runt dåliga frågeplaner och optimerarbuggar, särskilt de saker som många verkar tro bara kan lösas med tips:
- join_collapse_limit: Detta justerar hur mycket flexibilitet optimeraren har för att omordna sammanfogningar av flera tabeller. Normalt försöker den alla möjliga kombinationer när anslutningar kan arrangeras om (vilket är för det mesta, om du inte använder en yttre koppling). Att sänka join_collapse_limit, kanske till och med till 1, tar bort en del av eller hela denna flexibilitet. Med den inställd på 1 får du anslutningarna i den ordning du skrev dem i, punkt. Att planera ett stort antal kopplingar är en av de svåraste sakerna för optimeraren att göra; varje koppling förstorar fel i uppskattningar och ökar frågeplaneringstiden. Om den underliggande karaktären hos din data gör det uppenbart vilken ordningsföljd som bör ske, och du inte förväntar dig att det någonsin kommer att förändras, kan du låsa den med den här parametern när du väl har kommit fram till rätt ordning.
- random_page_cost: Med standardvärdet 4.0 ställer den här parametern in hur dyrt det är att söka efter en disk för att hitta en slumpmässig sida på disken, i förhållande till ett referensvärde på 1.0. Om du nu mäter förhållandet mellan slumpmässig och sekventiell I/O på vanliga hårddiskar, kommer du att upptäcka att siffran är närmare 50. Så varför 4.0? För det första, för att det har fungerat bättre än större värden i communitytestning. För det andra kommer i många fall särskilt indexdata att cachelagras i minnet, vilket gör den effektiva kostnaden för att läsa dessa värden lägre. Om, till exempel, ditt index är 90 % cachat i RAM, betyder det att du 10 % av tiden kommer att göra operationen som är 50 gånger så dyr; det skulle göra din effektiva random_page_cost till cirka 5. Den här typen av verkliga situationer är anledningen till att standarden är vettig där den är. Jag ser normalt att populära index får>95% cache i minnet. Om ditt index faktiskt är mycket mer sannolikt än så att allt finns i RAM, kan det vara ett rimligt val att sänka random_page_cost hela vägen ner till strax över 1.0, för att återspegla att det inte är dyrare än någon annan läsning. Samtidigt kan slumpmässiga sökningar på ett riktigt upptaget system vara mycket dyrare än vad du förväntar dig av att bara titta på enanvändarsimuleringar. Jag har varit tvungen att sätta random_page_cost så högt som 60 för att få databasen att sluta använda index när planeraren missuppskattade hur dyra de skulle bli. Vanligtvis kommer den situationen från ett känslighetsuppskattningsfel från planerarens sida - om du skannar mer än cirka 20 % av en tabell vet planeraren att använda en sekventiell skanning kommer att vara mycket effektivare än en indexskanning. Den fula situationen där jag var tvungen att tvinga det beteendet att hända mycket tidigare än så uppstod när planeraren förväntade sig att 1 % av raderna skulle returneras, men det var faktiskt närmare 15 %.
- work_mem: Justerar hur mycket minne som är tillgängligt för frågor som utför sortering, hashning och liknande minnesbaserade operationer. Detta är bara en grov riktlinje för frågor, inte en hård gräns, och en enskild klient kan sluta använda multiplar av work_mem när en fråga körs. Följaktligen måste du vara försiktig så att du inte ställer in detta värde för högt i postgresql.conf-filen. Vad du kan göra istället, ställer den in det innan du kör en fråga som verkligen drar nytta av att ha extra minne för att hålla sorterings- eller hashdata. Du kan ibland hitta dessa frågor från att logga långsamma med log_min_duration_statement. Du kan också hitta dem genom att aktivera log_temp_files, som loggar varje gång work_mem är för liten, och därför spills sorteringsoperationer till disken istället för att ske i minnet.
- OFFSET 0: PostgreSQL kommer att ordna om underfrågor till formen av en join, så att den sedan kan använda den vanliga logiken för join-ordning för att optimera den. I vissa fall kan det beslutet vara riktigt dåligt, eftersom det som folk brukar skriva som delfrågor verkar lite svårare att uppskatta av någon anledning (jag säger det baserat på antalet sådana besvärliga frågor jag ser). Ett lurigt knep du kan göra för att förhindra denna logik är att sätta OFFSET 0 i slutet av underfrågan. Detta ändrar inte resultaten, men om du infogar typen av Limit-fråganod som används för att köra OFFSET förhindrar du omarrangering. Underfrågan kommer då alltid att köras på det sätt som de flesta förväntar sig – som sin egen isolerade frågenod.
- enable_seqscan, enable_indexscan, enable_bitmapscan: Att stänga av en av dessa funktioner för att slå upp rader i en tabell är en ganska stor hammare för att starkt rekommendera att undvika den typen av skanning (inte alltid förhindra det – om det inte finns något sätt att genomföra din plan men en seqscan, får du en seqscan även om parametrarna är avstängda). Det viktigaste jag rekommenderar dessa för är att inte fixa frågor, det är att experimentera med EXPLAIN och se varför den andra typen av skanning föredrogs.
- enable_nestloop, enable_hashjoin, enable_mergejoin: Om du misstänker att ditt problem är den typ av join som används snarare än hur tabellerna läses, försök att stänga av typen du ser i din plan med någon av dessa parametrar och kör sedan EXPLAIN på nytt. Fel i känslighetsuppskattningar kan lätt få en join att verka mer eller mindre effektiv än den verkligen är. Och återigen, att se hur planen ändras med den nuvarande anslutningsmetoden inaktiverad kan vara mycket informativt för varför man beslutade sig för den i första hand.
- enable_hashagg, enable_material: Dessa funktioner är relativt nya för PostgreSQL. Att använda Hash Aggregation aggressivt introducerades i version 8.4 och mer aggressiv materialisering i 9.0. Om du ser den typen av noder i din EXPLAIN
utdata och de verkar göra något fel, eftersom den här koden är så mycket nyare är det lite mer sannolikt att det har en begränsning eller bugg än några av de äldre funktionerna. Om du hade en plan som fungerade bra i äldre versioner av PostgreSQL, men använder en av dessa nodtyper och verkar prestera mycket sämre som ett resultat, kan inaktivering av dessa funktioner ibland återgå till det tidigare beteendet - samt lysa lite ljus på varför optimeraren gjorde fel som användbar feedback. Observera att det i allmänhet är så som mer avancerade funktioner tenderar att introduceras i PostgreSQL: med möjlighet att stänga av det i felsökningssyfte, om det visar sig vara en planregression i förhållande till hur tidigare versioner körde saker. - cursor_tuple_fraction: Om du inte tänker läsa alla rader tillbaka från en fråga, bör du använda en markör för att implementera det. I så fall försöker optimeraren prioritera om den ger dig den första raden snabbt tillbaka, eller om den föredrar att optimera hela frågan, baserat på denna parameter. Som standard antar databasen att du kommer att läsa 10 % av frågan tillbaka igen när du använder en markör. Om du justerar den här parametern kan du förskjuta den mot att förvänta dig att du ska läsa mindre eller mer än så.
Alla dessa parametrar och frågejusteringar bör övervägas triagejusteringar. Du vill inte köra med dessa på plats för alltid (förutom kanske join_collapse_limit). Du använder dem för att komma ur ett problem, och sedan kommer du förhoppningsvis att ta reda på vad den verkliga bakomliggande orsaken till den dåliga planen är – dålig statistik, optimeringsbegränsning/bugg eller något annat – och sedan lösa problemet från det hållet. Ju mer du driver optimerarens beteende i en riktning, desto mer exponerad är du för framtida förändringar i din data vilket gör att push inte längre är korrekt. Om du använder dem rätt, som ett sätt att studera varför du fick fel plan (tillvägagångssättet jag använde i kapitlet om frågeoptimering i PostgreSQL 9.0 High Performance), bör sättet du tipsar om saker och ting i PostgreSQL resultera i att du lämnar varje körning- in med dåligt optimerarbeteende lite mer kunnig om hur man undviker den typen av problem i framtiden