Разбиране на разходите за Postgres EXPLAIN
EXPLAIN
е много полезно за разбиране на производителността на заявка в Postgres. Той връща плана за изпълнение, генериран от PostgreSQL плановик на заявки за даден израз. EXPLAIN
командата указва дали таблиците, посочени в израз, ще бъдат търсени чрез индексно сканиране или последователно сканиране.
Някои от първите неща, които ще забележите, когато преглеждате изхода на EXPLAIN
командата са статистическите данни за разходите, така че е естествено да се чудите какво означават, как се изчисляват и как се използват.
Накратко, PostgreSQL планирането на заявки оценява колко време ще отнеме заявката (в произволна единица), както с начална цена, така и с обща цена за всяка операция. Повече за това по-късно. Когато има множество опции за изпълнение на заявка, той използва тези разходи, за да избере най-евтината и следователно да се надяваме най-бързата опция.
В каква единица са разходите?
Разходите са в произволна единица. Често срещано недоразумение е, че са в милисекунди или някаква друга единица време, но това не е така.
Разходните единици са закотвени (по подразбиране) към една последователно четене на страница, струваща 1,0 единици (seq_page_cost
). Всеки обработен ред добавя 0,01 (cpu_tuple_cost
), и всяко непоследователно четене на страница добавя 4.0 (random_page_cost
). Има още много константи като тази, всички от които могат да се конфигурират. Последният е особено често срещан кандидат, поне при съвременния хардуер. Ще разгледаме това повече след малко.
Начални разходи
Първите числа, които виждате след cost=
са известни като „начална цена“. Това е приблизително колко време ще отнеме за извличане на първия ред . Като такава, началната цена на една операция включва разходите за нейните деца.
За последователно сканиране цената на стартиране обикновено ще бъде близка до нула, тъй като може да започне да извлича редове веднага. За операция за сортиране тя ще бъде по-висока, защото голяма част от работата трябва да бъде извършена, преди редовете да могат да започнат да се връщат.
За да разгледаме пример, нека създадем проста тестова таблица с 1000 потребителски имена:
СЪЗДАВАЙТЕ ТАБЛИЦА потребители ( id bigint ГЕНЕРИРАН ВИНАГИ КАТО ПЪРВИЧЕН КЛЮЧ НА ИДЕНТИЧНОСТ, текст на потребителското име НЕ НУЛЕН); ВЪВЕТЕ В потребители (потребителско име) ИЗБЕРЕТЕ 'лице' || nFROM generate_series(1, 1000) КАТО n;АНАЛИЗИРАНЕ на потребители;
Нека да разгледаме прост план за заявка с няколко операции:
ОБЯСНИТЕ ИЗБИРАНЕ * ОТ потребители ПОРЪЧАЙТЕ ПО потребителско име; ПЛАН НА ЗАЯВКАТА |---------------------------------- ---------------------------+Сортиране (цена=66,83..69,33 реда=1000 ширина=17) | Ключ за сортиране:потребителско име | -> Последователно сканиране на потребители (цена=0.00..17.00 реда=1000 ширина=17)|
В горния план за заявка, както се очаква, прогнозната цена за изпълнение на оператора за Seq Scan
е 0.00
и за Sort
е 66.83
.
Общи разходи
Втората статистика за разходите, след началната цена и двете точки, е известна като „обща цена“. Това е приблизителна оценка за това колко време ще отнеме връщането на всички редове .
Нека отново разгледаме този примерен план за заявка:
ПЛАН НА ЗАЯВКАТА |----------------------------------------------------- ------------------+Сортиране (цена=66,83..69,33 реда=1000 ширина=17) | Ключ за сортиране:потребителско име | -> Последователно сканиране на потребители (цена=0.00..17.00 реда=1000 ширина=17)|
Можем да видим, че общата цена на Seq Scan
операцията е 17.00
. За Sort
операция е 69,33, което не е много повече от началната му цена (както се очакваше).
Общите разходи обикновено включват разходите за операциите, които ги предхождат. Например, общата цена на операцията по сортиране по-горе включва тази на Seq Scan.
Важно изключение е LIMIT
клаузи, които планиращият използва, за да прецени дали може да се прекъсне по-рано. Ако се нуждае само от малък брой редове, условията за които са общи, той може да изчисли, че по-простият избор на сканиране е по-евтин (вероятно ще бъде по-бърз).
Например:
ОБЯСНЯВАНЕ НА ИЗБИРАНЕ * ОТ ОГРАНИЧЕНИЕ НА Потребителите 1; ПЛАН ЗА ЗАЯВКА |----------------------------------- -------------------------+Ограничение (цена=0,00..0,02 реда=1 ширина=17) | -> Последователно сканиране на потребители (цена=0.00..17.00 реда=1000 ширина=17)|
Както можете да видите, общата цена, отчетена за възела Seq Scan, все още е 17,00, но се съобщава, че пълната цена на операцията Limit е 0,02. Това е така, защото планиращият очаква, че ще трябва да обработи само 1 ред от 1000, така че цената в този случай се оценява на 1000-та от общия брой.
Как се изчисляват разходите
За да изчисли тези разходи, Postgres планерът на заявки използва както константи (някои от които вече видяхме), така и метаданни за съдържанието на базата данни. Метаданните често се наричат „статистически данни“.
Статистическите данни се събират чрез ANALYZE
(да не се бърка с EXPLAIN
параметър със същото име) и се съхранява в pg_statistic
. Те също се обновяват автоматично като част от автоматичното вакуумиране.
Тези статистически данни включват редица много полезни неща, като приблизително броя на редовете във всяка таблица и кои са най-често срещаните стойности във всяка колона.
Нека разгледаме прост пример, използвайки същите данни от заявката, както преди:
ОБЯСНЯВАЙТЕ ИЗБРАН брой(*) ОТ потребители; ПЛАН НА ЗАЯВКАТА |---------------------------------- -------------------------+Агрегат (цена=19,50..19,51 реда=1 ширина=8) | -> Последователно сканиране на потребители (цена=0,00..17,00 реда=1000 ширина=0)|
В нашия случай статистиката на планировчика предполага, че данните за таблицата се съхраняват в рамките на 7 страници (или блокове) и че ще бъдат върнати 1000 реда. Разходните параметри seq_page_cost
, cpu_tuple_cost
и cpu_operator_cost
бяха оставени с техните настройки по подразбиране от 1
, 0.01
и 0.0025
съответно.
Като такъв, общата цена на Seq Scan беше изчислена като:
Обща цена на Seq Scan=(приблизително четене на последователна страница * seq_page_cost) + (приблизително върнати редове * cpu_tuple_cost)=(7 * 1) + (1000 * 0,01)=7 + 10,00=17,00
А за агрегата като:
Обща цена на Aggregate=(цена на Seq Scan) + (приблизителни обработени редове * cpu_operator_cost) + (приблизителни върнати редове * cpu_tuple_cost)=(17.00) + (1000 * 0.0025) + (1 * 0.01) =+ 17.00. + 0,01=19,51
Как планиращият използва разходите
Тъй като знаем, че Postgres ще избере плана за заявка с най-ниска обща цена, можем да използваме това, за да се опитаме да разберем избора, който е направил. Например, ако заявка не използва индекс, който очаквате, можете да използвате настройки като enable_seqscan
за масово обезкуражаване на определени избори на план за заявка. До този момент не бива да се учудвате да чуете, че настройките като тази работят чрез увеличаване на разходите!
Номарата на редовете са изключително важна част от оценката на разходите. Те се използват за изчисляване на оценки за различни поръчки за присъединяване, алгоритми за присъединяване, типове сканиране и др. Прогнозите за разходите на редове, които са изчерпани с партида, могат да доведат до това, че приблизителната оценка на разходите се отклонява с партида, което в крайна сметка може да доведе до избор на неоптимален план.
Използване на EXPLAIN ANALYZE за получаване на план за заявка
Когато пишете SQL изрази в PostgreSQL, ANALYZE
командата е от ключово значение за оптимизирането на заявките, което ги прави по-бързи и по-ефективни. В допълнение към показването на плана на заявката и оценките на PostgreSQL, EXPLAIN ANALYZE
опцията изпълнява заявката (внимавайте с UPDATE
и DELETE
!), и показва действителното време на изпълнение и броя на редовете за всяка стъпка в процеса на изпълнение. Това е необходимо за наблюдение на производителността на SQL.
Можете да използвате EXPLAIN ANALYZE
за да сравните прогнозния брой редове с действителните редове, върнати от всяка операция.
Нека да разгледаме пример, използвайки отново същите данни:
ПЛАН НА ЗАЯВКАТА |----------------------------------------------------- -------------------------------------------------- ------------+Сортиране (цена=66.83..69.33 реда=1000 ширина=17) (действително време=20.569..20.684 реда=1000 цикъла=1) | Ключ за сортиране:потребителско име | Метод на сортиране:quicksort Памет:102 kB | -> Последователно сканиране на потребители (цена=0.00..17.00 реда=1000 ширина=17) (действително време=0.048..0.596 реда=1000 цикъла=1)|Време за планиране:0.171 ms |Време на изпълнение:20.793 ms | предварително>Можем да видим, че общите разходи за изпълнение все още са 69,33, като по-голямата част от тях е операцията за сортиране, а 17,00 идват от последователното сканиране. Имайте предвид, че времето за изпълнение на заявката е малко под 21 мс.
Последователно сканиране спрямо индексно сканиране
Сега нека добавим индекс, за да се опитаме да избегнем този скъп вид на цялата таблица:
CREATE INDEX people_username_idx НА потребители (потребителско име);ОБЯСНЯВАНЕ НА АНАЛИЗ ИЗБОР * ОТ потребители ПОРЪЧАЙТЕ ПО потребителско име; ПЛАН ЗА ЗАЯВКА |----------------------- -------------------------------------------------- -------------------------------------------------- ------+Индексно сканиране с помощта на people_username_idx за потребители (цена=0.28..28.27 реда=1000 ширина=17) (действително време=0.052..1.494 реда=1000 цикъла=1)|Време за планиране:0.186 ms |Изпълнение Време:1,686 ms |Както можете да видите, плановникът на заявки вече е избрал индексно сканиране, тъй като общата цена на този план е 28,27 (по-ниска от 69,33). Изглежда, че индексното сканиране е било по-ефективно от последователното сканиране, тъй като времето за изпълнение на заявката вече е малко под 2 мс.
Помагане на планиращия да изчисли по-точно
Можем да помогнем на планиращия да изчисли по-точно по два начина:
- Помогнете му да събира по-добри статистически данни
- Настройте константите, които използва за изчисленията
Статистиката може да бъде особено лоша след голяма промяна на данните в таблица. Като такъв, когато зареждате много данни в таблица, можете да помогнете на Postgres, като стартирате ръчно
ANALYZE
върху него. Статистиката също не се запазва при надграждане на основна версия, така че това е друг важен момент да направите това.Естествено, таблиците също се променят с течение на времето, така че настройването на настройките за автоматично вакуумиране, за да се гарантира, че работи достатъчно често за вашето работно натоварване, може да бъде много полезно.
Ако имате проблеми с лошите оценки за колона с изкривено разпределение, може да се възползвате от увеличаването на количеството информация, която Postgres събира, като използвате
ALTER TABLE SET STATISTICS
команда или дориdefault_statistics_target
за цялата база данни.Друга често срещана причина за лоши оценки е, че по подразбиране Postgres ще приеме, че две колони са независими. Можете да коригирате това, като го помолите да събере корелационни данни за две колони от една и съща таблица чрез разширени статистически данни.
На фронта на постоянната настройка има много параметри, които можете да настроите, за да отговарят на вашия хардуер. Ако приемем, че работите на SSD дискове, най-вероятно ще искате като минимум да настроите настройката си за
random_page_cost
. Това по подразбиране е 4, което е 4 пъти по-скъпо отseq_page_cost
разгледахме по-рано. Това съотношение има смисъл при въртящи се дискове, но при SSD дисковете има тенденция да санкционира твърде много произволно I/O. Като такава настройка, по-близка до 1 или между 1 и 2, може да има повече смисъл. В ScaleGrid по подразбиране е 1.Мога ли да премахна разходите от плановете за заявки?
Поради много от причините, споменати по-горе, повечето хора оставят разходите включени, когато изпълняват
EXPLAIN
. Въпреки това, ако желаете, можете да ги изключите с помощта наCOSTS
параметър.ОБЯСНЯВАЙТЕ (ОТКЛЮЧЕНИ РАЗХОДИ) ИЗБЕРЕТЕ * ОТ Потребители ОГРАНИЧЕНИЕ 1; ПЛАН НА ЗАЯВКАТА |-----------------------+Limit | -> Последователно сканиране на потребители|Заключение
За да обобщим отново, разходите в плановете за заявки са оценки на Postgres за това колко време ще отнеме SQL заявка, в произволна единица.
Той избира плана с най-ниска обща цена въз основа на някои конфигурируеми константи и някои статистически данни, които е събрал.
Много е важно да му помогнете да оцени тези разходи по-точно, за да му помогнете да направи добър избор и да поддържа вашите заявки ефективни.
|