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Од табела-делот: Seq Scan, Bitmap Heap Scan или Index Scan. Но, во секој случај, овие „деца“ нема да бидат сложени прашања - вака може да се разликуваат овие јазли од Append во UNION.
PostgreSQL Query Profiler: како да се совпаднат планот и барањето

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

„Едноставни“ јазли за примање податоци

PostgreSQL Query Profiler: како да се совпаднат планот и барањето

Values Scan кореспондира во план VALUES во барањето.

Result е барање без FROM вид на SELECT 1. Или кога имате намерно лажен израз WHERE-блок (тогаш се појавува атрибутот 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 „мапа“ на SRF со исто име.

Но, со вгнездените прашања сè е покомплицирано - за жал, тие не секогаш се претвораат во 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, која има точно две деца - лево и десно. Ова, соодветно, е она што е „над“ вашето ПРИКЛУЧУВАЊЕ и што е напишано „подолу“ во барањето.

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

Ајде да користиме едноставна логика: ако имаме табели A и B кои се „спојуваат“ една со друга во планот, тогаш во барањето тие би можеле да бидат лоцирани или A-JOIN-BИли B-JOIN-A. Ајде да се обидеме да комбинираме вака, да се обидеме да комбинираме обратно, и така додека не останеме без такви парови.

Ајде да си го земеме синтаксното дрво, земи го нашиот план, погледни ги... не слични!
PostgreSQL Query Profiler: како да се совпаднат планот и барањето

Ајде да го прецртаме во форма на графикони - ох, веќе изгледа како нешто!
PostgreSQL Query Profiler: како да се совпаднат планот и барањето

Да забележиме дека имаме јазли кои истовремено имаат деца Б и Ц - не ни е грижа по кој редослед. Ајде да ги комбинираме и да ја превртиме сликата на јазолот.
PostgreSQL Query Profiler: како да се совпаднат планот и барањето

Ајде да погледнеме повторно. Сега имаме јазли со деца А и парови (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

Додадете коментар