Förstå kostnaden för Postgres EXPLAIN
EXPLAIN
är mycket användbart för att förstå prestandan för en Postgres-fråga. Den returnerar exekveringsplanen som genereras av PostgreSQL-frågeplaneraren för en given sats. EXPLAIN
kommandot anger om tabellerna som hänvisas till i en sats ska sökas igenom med en indexskanning eller en sekventiell skanning.
Några av de första sakerna du kommer att lägga märke till när du granskar resultatet av en EXPLAIN
kommandot är kostnadsstatistiken, så det är naturligt att undra vad de betyder, hur de beräknas och hur de används.
Kort sagt, PostgreSQL-frågeplaneraren uppskattar hur lång tid frågan kommer att ta (i en godtycklig enhet), med både en startkostnad och en total kostnad för varje operation. Mer om det senare. När den har flera alternativ för att utföra en fråga, använder den dessa kostnader för att välja det billigaste och därför förhoppningsvis snabbaste alternativet.
Vilken enhet ligger kostnaderna i?
Kostnaderna är i en godtycklig enhet. Ett vanligt missförstånd är att de är i millisekunder eller någon annan tidsenhet, men så är inte fallet.
Kostnadsenheterna är förankrade (som standard) till en enda sekventiell sida som läser kostar 1,0 enheter (seq_page_cost
). Varje rad som bearbetas lägger till 0,01 (cpu_tuple_cost
), och varje icke-sekventiell sida som läses lägger till 4.0 (random_page_cost
). Det finns många fler konstanter som denna, som alla är konfigurerbara. Den sista är en särskilt vanlig kandidat, åtminstone på modern hårdvara. Vi ska titta närmare på det om en stund.
Startkostnader
De första siffrorna du ser efter cost=
är kända som "startkostnaden". Det här är en uppskattning av hur lång tid det tar att hämta den första raden . Som sådan inkluderar startkostnaden för en operation kostnaden för dess barn.
För en sekventiell skanning kommer startkostnaden i allmänhet att vara nära noll, eftersom den kan börja hämta rader direkt. För en sorteringsoperation blir den högre eftersom en stor del av arbetet måste göras innan rader kan börja returneras.
För att titta på ett exempel, låt oss skapa en enkel testtabell med 1000 användarnamn:
SKAPA TABELL-användare ( id bigint GENERADES ALLTID SOM IDENTITETSPRIMÄRNYCKEL, användarnamnstext INTE NULL);INSERT INTO användare (användarnamn)VÄLJ 'person' || nFROM gener_series(1, 1000) AS n;ANALYSE users;
Låt oss ta en titt på en enkel frågeplan, med ett par operationer:
FÖRKLARA VÄLJ * FRÅN användare BESTÄLL EFTER användarnamn;FRÅGA PLAN |------------------------------------------------ ---------------------------+Sortera (kostnad=66.83..69.33 rader=1000 bredd=17) | Sorteringsnyckel:användarnamn | -> Seq Scan på användare (kostnad=0.00..17.00 rader=1000 width=17)|
I frågeplanen ovan, som förväntat, den beräknade utförandekostnaden för Seq Scan
är 0.00
, och för Sort
är 66.83
.
Totala kostnader
Den andra kostnadsstatistiken, efter startkostnaden och de två prickarna, är känd som den "totala kostnaden". Det här är en uppskattning av hur lång tid det kommer att ta att återställa alla rader .
Låt oss titta på exemplet på frågeplanen igen:
FRÅGAN PLAN |-------------------------------------------------------- ------------------+Sortera (kostnad=66.83..69.33 rader=1000 bredd=17) | Sorteringsnyckel:användarnamn | -> Seq Scan på användare (kostnad=0.00..17.00 rader=1000 width=17)|
Vi kan se att den totala kostnaden för Seq Scan
operationen är 17.00
. För Sort
driften är 69,33, vilket inte är mycket mer än dess startkostnad (som förväntat).
Totala kostnader inkluderar vanligtvis kostnaden för de operationer som föregår dem. Till exempel inkluderar den totala kostnaden för sorteringsoperationen ovan kostnaden för Seq Scan.
Ett viktigt undantag är LIMIT
klausuler, som planeraren använder för att uppskatta om den kan avbryta tidigt. Om den bara behöver ett litet antal rader, vars villkor är vanliga, kan den beräkna att ett enklare skanningsval är billigare (förmodligen snabbare).
Till exempel:
FÖRKLARA VÄLJ * FRÅN användare LIMIT 1;FRÅGEPLAN |------------------------------------------- --------------------------+Limit (kostnad=0,00..0.02 rader=1 bredd=17) | -> Seq Scan på användare (kostnad=0.00..17.00 rader=1000 width=17)|
Som du kan se är den totala kostnaden som rapporteras på Seq Scan-noden fortfarande 17,00, men den totala kostnaden för Limit-operationen rapporteras vara 0,02. Detta beror på att planeraren förväntar sig att den bara kommer att behöva bearbeta 1 rad av 1000, så kostnaden, i det här fallet, uppskattas till 1000:e av totalen.
Hur kostnaderna beräknas
För att beräkna dessa kostnader använder Postgres frågeplanerare både konstanter (av vilka vi redan har sett) och metadata om innehållet i databasen. Metadata kallas ofta för "statistik".
Statistik samlas in via ANALYZE
(inte att förväxla med EXPLAIN
parameter med samma namn) och lagras i pg_statistic
. De uppdateras också automatiskt som en del av autovakuum.
Denna statistik inkluderar ett antal mycket användbara saker, som ungefär antalet rader varje tabell har och vilka de vanligaste värdena i varje kolumn är.
Låt oss titta på ett enkelt exempel med samma frågedata som tidigare:
FÖRKLARA VÄLJ ANTAL(*) FRÅN användare;FRÅGA PLAN |------------------------------------------------ --------------------------+Aggregerat (kostnad=19.50..19.51 rader=1 bredd=8) | -> Seq Scan på användare (kostnad=0.00..17.00 rader=1000 width=0)|
I vårt fall föreslog planerarens statistik att data för tabellen lagrades inom 7 sidor (eller block) och att 1000 rader skulle returneras. Kostnadsparametrarna seq_page_cost
, cpu_tuple_cost
och cpu_operator_cost
lämnades med sina standardvärden 1
, 0.01
och 0.0025
respektive.
Som sådan beräknades den totala kostnaden för Seq Scan som:
Total kostnad för Seq Scan=(uppskattad sekventiell sida läser * seq_page_cost) + (uppskattade rader returnerade * cpu_tuple_cost)=(7 * 1) + (1000 * 0,01)=7 + 10,00=17,00
Och för aggregatet som:
Total kostnad för Aggregate=(kostnad för Seq Scan) + (uppskattade rader bearbetade * cpu_operator_cost) + (uppskattade rader returnerade * cpu_tuple_cost)=(17,00) + (1000 * 0,0025) + (1 * 0,01) =+ 17,00 =+ 17,00 + 0,01=19,51
Hur planeraren använder kostnaderna
Eftersom vi vet att Postgres kommer att välja frågeplanen med den lägsta totala kostnaden, kan vi använda den för att försöka förstå de val den har gjort. Om en fråga till exempel inte använder ett index som du förväntar dig kan du använda inställningar som enable_seqscan
att kraftigt motverka vissa frågeplansval. Vid det här laget borde du inte bli förvånad över att höra att inställningar som dessa fungerar genom att öka kostnaderna!
Radnummer är en extremt viktig del av kostnadsuppskattningen. De används för att beräkna uppskattningar för olika sammanfogningsorder, sammanfogningsalgoritmer, skanningstyper och mer. Radkostnadsuppskattningar som är ute efter mycket kan leda till att kostnadsuppskattningen blir borta med mycket, vilket i slutändan kan resultera i att ett suboptimalt planval görs.
Använd EXPLAIN ANALYZE för att få en frågeplan
När du skriver SQL-satser i PostgreSQL visas ANALYZE
kommandot är nyckeln till att optimera frågor, vilket gör dem snabbare och mer effektiva. Förutom att visa frågeplanen och PostgreSQL-uppskattningar, visas EXPLAIN ANALYZE
alternativet utför frågan (var försiktig med UPDATE
och DELETE
!), och visar den faktiska exekveringstiden och antal rader för varje steg i exekveringsprocessen. Detta är nödvändigt för att övervaka SQL-prestanda.
Du kan använda EXPLAIN ANALYZE
för att jämföra det uppskattade antalet rader med de faktiska rader som returneras av varje operation.
Låt oss titta på ett exempel och använda samma data igen:
FRÅGAN PLAN |-------------------------------------------------------- -------------------------------------------------- -------------+Sortera (kostnad=66.83..69.33 rader=1000 bredd=17) (faktisk tid=20.569..20.684 rader=1000 loopar=1) | Sorteringsnyckel:användarnamn | Sorteringsmetod:quicksort Minne:102kB | -> Seq Scan på användare (kostnad=0.00..17.00 rader=1000 bredd=17) (faktisk tid=0.048..0.596 rader=1000 loopar=1)|Planeringstid:0,171 ms |Utförandetid:20,793 ms | före>Vi kan se att den totala exekveringskostnaden fortfarande är 69,33, där majoriteten är sorteringsoperationen och 17,00 kommer från den sekventiella skanningen. Observera att exekveringstiden för frågan är strax under 21 ms.
Sekventiell skanning vs. Indexskanning
Låt oss nu lägga till ett index för att försöka undvika den där kostsamma typen av hela tabellen:
SKAPA INDEX people_username_idx PÅ användare (användarnamn);EXPLAIN ANALYSE SELECT * FROM användare BESTÄLL EFTER användarnamn;FRÅGA PLAN |---------------------------- -------------------------------------------------- -------------------------------------------------- ------+Index Skanna med people_username_idx på användare (kostnad=0.28..28.27 rader=1000 bredd=17) (faktisk tid=0.052..1.494 rader=1000 loopar=1)|Planeringstid:0,186 ms |Utförande Tid:1,686 ms |Som du kan se har frågeplaneraren nu valt en Index Scan, eftersom den totala kostnaden för den planen är 28,27 (lägre än 69,33). Det verkar som att indexsökningen var effektivare än den sekventiella genomsökningen, eftersom exekveringstiden för frågan nu är strax under 2ms.
Hjälper planeraren att uppskatta mer exakt
Vi kan hjälpa planeraren att uppskatta mer exakt på två sätt:
- Hjälp den att samla in bättre statistik
- Justera konstanterna som den använder för beräkningarna
Statistiken kan vara särskilt dålig efter en stor förändring av data i en tabell. Som sådan, när du laddar mycket data i en tabell, kan du hjälpa Postgres genom att köra en manuell
ANALYZE
på det. Statistiken kvarstår inte heller över en större versionsuppgradering, så det är en annan viktig tid att göra detta.Naturligtvis ändras tabeller också över tiden, så att justera autovakuuminställningarna för att se till att de körs tillräckligt ofta för din arbetsbelastning kan vara till stor hjälp.
Om du har problem med dåliga uppskattningar för en kolumn med skev fördelning kan du dra nytta av att öka mängden information som Postgres samlar in genom att använda
ALTER TABLE SET STATISTICS
kommandot, eller till och meddefault_statistics_target
för hela databasen.En annan vanlig orsak till dåliga uppskattningar är att Postgres som standard antar att två kolumner är oberoende. Du kan fixa detta genom att be den samla in korrelationsdata på två kolumner från samma tabell via utökad statistik.
På den konstanta inställningsfronten finns det många parametrar du kan ställa in för att passa din hårdvara. Om du antar att du kör på SSD:er kommer du troligen åtminstone att vilja justera din inställning för
random_page_cost
. Detta är standard på 4, vilket är 4 gånger dyrare änseq_page_cost
vi tittade på tidigare. Detta förhållande var vettigt på snurrande diskar, men på SSD:er tenderar det att straffa slumpmässig I/O för mycket. Som sådan kan en inställning närmare 1, eller mellan 1 och 2, vara mer meningsfull. På ScaleGrid har vi som standard 1.Kan jag ta bort kostnaderna från frågeplaner?
Av många av skälen som nämnts ovan låter de flesta kostnaderna vara kvar när de kör
EXPLAIN
. Men om du vill kan du stänga av dem medCOSTS
parameter.FÖRKLARA (KOSTNADER AV) VÄLJ * FRÅN användare LIMIT 1;FRÅGEPLAN |----------------------------+Gräns | -> Seq Scan på användare|Slutsats
För att få ett nytt tak är kostnaderna i frågeplaner Postgres uppskattningar för hur lång tid en SQL-fråga kommer att ta, i en godtycklig enhet.
Den väljer planen med den lägsta totala kostnaden, baserat på några konfigurerbara konstanter och viss statistik som den har samlat in.
Att hjälpa den att uppskatta dessa kostnader mer exakt är mycket viktigt för att hjälpa den att göra bra val och hålla dina frågor effektiva.
|