PostgreSQL Query Profiler: Как да съпоставите план и заявка

Много, които вече използват обяснение.tensor.ru - нашата услуга за визуализация на план PostgreSQL може да не е наясно с една от своите суперсили - да превърне трудно за четене парче от дневника на сървъра ...

PostgreSQL Query Profiler: Как да съпоставите план и заявка
... в красиво проектирана заявка с контекстуални съвети за съответните възли на плана:

PostgreSQL Query Profiler: Как да съпоставите план и заявка
В този препис на втората част от негов доклад на PGConf.Russia 2020 Ще ви кажа как успяхме да го направим.

Транскриптът на първата част, посветен на типични проблеми с изпълнението на заявки и техните решения, можете да намерите в статията „Рецепти за болни SQL заявки“.



Първо, нека направим оцветяването - и вече няма да оцветяваме плана, вече сме го нарисували, вече е красиво и разбираемо, но искането.

Струваше ни се, че заявката, извадена от дневника с неформатиран „лист“, изглежда много грозна и следователно неудобна.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Особено когато разработчиците „залепват“ тялото на заявката в кода (това, разбира се, е анти-модел, но се случва) в един ред. Ужас!

Нека го нарисуваме някак по-красиво.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

И ако можем да го нарисуваме красиво, тоест да разглобим и сглобим тялото на заявката обратно, тогава можем да „прикрепим“ подсказка към всеки обект на тази заявка - какво се е случило в съответната точка от плана.

Синтаксисно дърво на заявката

За да направите това, заявката трябва първо да бъде анализирана.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Защото имаме ядрото на системата работи на NodeJS, тогава направихме модул за него, можете намерете го в github. Всъщност това са разширени "обвързвания" към вътрешността на самия анализатор на PostgreSQL. Това означава, че граматиката е просто двоично компилирана и обвързванията се правят към нея от NodeJS. Взехме модули на други хора като основа - тук няма голяма тайна.

Подаваме тялото на входната заявка към нашата функция - на изхода получаваме анализирано синтактично дърво под формата на JSON обект.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Сега можем да преминем през това дърво в обратна посока и да сглобим заявката с отстъпите, оцветяването, форматирането, които искаме. Не, това не може да се конфигурира, но решихме, че ще е удобно.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Картографиране на възли на заявка и план

Сега нека видим как можем да комбинираме плана, който анализирахме в първата стъпка, и заявката, която анализирахме във втората.

Да вземем прост пример – имаме заявка, която формира CTE и го чете два пъти. Той генерира такъв план.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

CTE

Ако го разгледате внимателно, че преди 12-та версия (или като се започне от нея с ключовата дума MATERIALIZED) образуване CTE е безусловна бариера за планировчика.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

И това означава, че ако видим някъде в заявката генерирането на CTE и някъде в плана възела CTE, тогава тези възли уникално се „бият“ помежду си, можем веднага да ги комбинираме.

Задача "със звездичка"Забележка: CTE могат да бъдат вложени.
PostgreSQL Query Profiler: Как да съпоставите план и заявка
Има много лошо вложени и дори едноименни. Например, можете вътре CTE A правя CTE X, и на същото ниво вътре CTE B направи отново CTE X:

WITH A AS (
  WITH X AS (...)
  SELECT ...
)
, B AS (
  WITH X AS (...)
  SELECT ...
)
...

Когато сравнявате, трябва да разберете това. Много е трудно да разберете това с "очи" - дори да видите плана, дори да видите тялото на заявката. Ако вашето CTE поколение е сложно, вложено, заявките са големи, тогава то е напълно несъзнателно.

СЪЮЗ

Ако имаме ключова дума в заявката UNION [ALL] (операторът за свързване на две проби), тогава той съответства в плана на който и да е възел Append, или някои Recursive Union.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Това, което е "отгоре" UNION - това е първото дете на нашия възел, което е "отдолу" - второто. Ако през UNION ние сме "залепили" няколко блока наведнъж, тогава Append-възелът все още ще има само един, но няма да има две деца, а много - по ред, съответно:

  (...) -- #1
UNION ALL
  (...) -- #2
UNION ALL
  (...) -- #3

Append
  -> ... #1
  -> ... #2
  -> ... #3

Задача "със звездичка": вътре в рекурсивно генериране на извличане (WITH RECURSIVE) също може да бъде повече от един UNION. Но само последният блок след последния винаги е рекурсивен UNION. Всичко по-горе е едно, но различно UNION:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
  (...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...

Такива примери също трябва да могат да "залепят". В този пример виждаме това UNION-сегментите в нашата заявка бяха 3 бр. Съответно един UNION соответствует Append- възел, а другият - Recursive Union.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Данни за четене и запис

Всичко, изложено, сега знаем коя част от заявката отговаря на коя част от плана. И в тези парчета можем лесно и естествено да намерим онези обекти, които са „четими“.

От гледна точка на заявката не знаем дали това е таблица или CTE, но те са обозначени с един и същи възел RangeVar. И в „четливия“ план това също е доста ограничен набор от възли:

  • Seq Scan on [tbl]
  • Bitmap Heap Scan on [tbl]
  • Index [Only] Scan [Backward] using [idx] on [tbl]
  • CTE Scan on [cte]
  • Insert/Update/Delete on [tbl]

Знаем структурата на плана и заявката, знаем съответствието на блоковете, знаем имената на обектите - правим еднозначна съпоставка.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

отново задача "със звездичка". Взимаме заявка, изпълняваме я, нямаме никакви псевдоними - просто я четем два пъти от един CTE.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Нека да разгледаме плана - какъв е проблемът? Защо имахме псевдоним? Не сме го поръчвали. Откъде има такъв "номер"?

PostgreSQL го добавя сам. Просто трябва да разберете това точно такъв псевдоним за нас, за целите на сравнението с плана, няма смисъл, просто се добавя тук. Да не му обръщаме внимание.

Вторият задача "със звездичка": ако четем от разделена таблица, тогава ще получим възел Append или Merge Append, който ще се състои от голям брой "деца", като всяко от тях ще бъде няколко Scan'om от секцията за таблица: Seq Scan, Bitmap Heap Scan или Index Scan. Но във всеки случай тези „деца“ няма да бъдат сложни заявки - по този начин тези възли могат да бъдат разграничени от Append в UNION.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Ние също разбираме такива възли, събираме ги „на една купчина“ и казваме: „всичко, което четете от megatable, е точно тук и надолу по дървото".

„Прости“ възли за събиране на данни

PostgreSQL Query Profiler: Как да съпоставите план и заявка

Values Scan в план съответства VALUES в искането.

Result е заявка без FROM като SELECT 1. Или когато имате фалшив израз WHERE-block (тогава се появява атрибутът One-Time Filter):

EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1

Result  (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
  One-Time Filter: false

Function Scan „Мапяция“ на едноименния СРФ.

Но с вложените заявки всичко е по-сложно - за съжаление те не винаги се превръщат в InitPlan/SubPlan. Понякога се превръщат в ... Join или ... Anti Join, особено когато пишете нещо като WHERE NOT EXISTS .... И не винаги е възможно да се комбинира там - в текста на плана няма оператори, съответстващи на възлите на плана.

отново задача "със звездичка": някои VALUES в искането. В този случай и в плана ще получите няколко възела Values Scan.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Наставките "номер" ще ви помогнат да ги разграничите един от друг - добавя се точно в реда, в който съответстват VALUES-блокове в хода на заявката отгоре надолу.

Обработка на данни

Изглежда, че всичко в нашата заявка беше подредено - само Limit.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Но тук всичко е просто - такива възли като Limit, Sort, Aggregate, WindowAgg, Unique Те „картират“ едно към едно към съответните оператори в заявката, ако ги има. Няма "звезди" и трудности.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

ПРИСЪЕДИНЕТЕ СЕ КЪМ

Трудностите възникват, когато искаме да комбинираме JOIN помежду си. Това не винаги е възможно, но е възможно.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

От гледна точка на анализатора на заявки имаме възел JoinExpr, която има точно две деца - ляво и дясно. Това съответно е това, което е „над“ вашия JOIN и това, което е написано „под“ него в заявката.

А от гледна точка на плана това са двама потомци на едн * Loop/* Join-възел. Nested Loop, Hash Anti Join...е нещо такова.

Нека използваме проста логика: ако имаме таблици A и B, които се „съединяват“ една към друга в плана, тогава в заявката те могат да бъдат разположени или A-JOIN-BИли B-JOIN-A. Да се ​​опитаме да комбинираме така, да опитаме да комбинираме по обратния начин и така докато свършат такива двойки.

Вземете нашето синтактично дърво, вземете нашия план, погледнете ги... не изглежда така!
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Нека го преначертаем под формата на графики - о, нещо вече е станало подобно на нещо!
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Нека забележим, че имаме възли, които имат деца B и C едновременно - за нас няма значение в какъв ред. Нека ги комбинираме и обърнем снимката на възела.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Нека погледнем отново. Сега имаме възли с деца A и двойки (B + C) - съвместими с тях.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Страхотен! Оказва се, че ние сме тези двамата JOIN от заявката с възлите на плана бяха успешно комбинирани.

Уви, този проблем не винаги е решен.
PostgreSQL Query Profiler: Как да съпоставите план и заявка

Например, ако искането A JOIN B JOIN C, и в плана първо бяха свързани „крайните“ възли A и C. Но в заявката няма такъв оператор, няма какво да подчертаем, няма с какво да обвържем подсказката. Същото със "запетая", когато пишете A, B.

Но в повечето случаи почти всички възли успяват да се „развържат“ и да получат такова профилиране отляво навреме - буквално, като в Google Chrome, когато анализирате JavaScript код. Можете да видите колко дълго всеки ред и всеки оператор са "изпълнени".
PostgreSQL Query Profiler: Как да съпоставите план и заявка

И за да ви улесним да използвате всичко това, сме направили съхранение архива, където можете да запазите и след това да намерите вашите планове заедно със свързани заявки или да споделите връзка с някого.

Ако просто трябва да приведете нечетлива заявка в адекватна форма, използвайте нашият "нормализатор".

PostgreSQL Query Profiler: Как да съпоставите план и заявка

Източник: www.habr.com

Добавяне на нов коментар