Відповіді:
Пояснення_EXPLAIN.pdf теж може допомогти.
Частина, яку я завжди вважав заплутаною, - це вартість запуску та загальна вартість. Я Google це кожен раз, коли я забуваю про це, що повертає мене сюди, що не пояснює різниці, саме тому я пишу цю відповідь. Це те, що я зрозумів із документації PostgresEXPLAIN
, пояснивши, як я це розумію.
Ось приклад програми, яка керує форумом:
EXPLAIN SELECT * FROM post LIMIT 50;
Limit (cost=0.00..3.39 rows=50 width=422)
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Ось графічне пояснення від PgAdmin:
(Коли ви використовуєте PgAdmin, ви можете навести мишу на компонент, щоб прочитати деталі витрат.)
Вартість представлена у вигляді кортежу, наприклад , витрати на LIMIT
це cost=0.00..3.39
і вартість сканування послідовно post
знаходиться cost=0.00..15629.12
. Перше число в кортежі - це вартість запуску, а друге - загальна вартість . Оскільки я використовував EXPLAIN
і ні EXPLAIN ANALYZE
, ці витрати є кошторисом, а не фактичними заходами.
Як ускладнення, витрати кожного "батьківського" вузла включають витрати його дочірніх вузлів. У текстовому поданні дерево представлене відступом, наприклад, LIMIT
є батьківським вузлом і Seq Scan
є його дочірньою. У представленні PgAdmin стрілки вказують від дитини до батьків - напрям потоку даних - що може бути контрінтуїтивним, якщо ви знайомі з теорією графіків.
У документації зазначено, що витрати включають усі дочірні вузли, але зауважте, що загальна вартість батьків 3.39
значно менша, ніж загальна вартість його дитини 15629.12
. Загальна вартість не включена, тому що такий компонент LIMIT
не потребує опрацювання всього його вводу. Дивіться EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
приклад у документації PostgresEXPLAIN
.
У наведеному вище прикладі час запуску для обох компонентів дорівнює нулю, тому що жоден компонент не потребує жодної обробки перед тим, як він починає писати рядки: послідовне сканування зчитує перший рядок таблиці та випускає його. LIMIT
Читає перший рядок , а потім випромінює його.
Коли компонент повинен зробити багато обробки, перш ніж він може почати виводити будь-які рядки? Можливих причин дуже багато, але давайте розглянемо один чіткий приклад. Ось такий самий запит, який раніше, але тепер містить ORDER BY
пункт:
EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50;
Limit (cost=23283.24..23283.37 rows=50 width=422)
-> Sort (cost=23283.24..23859.27 rows=230412 width=422)
Sort Key: body
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
І графічно:
Ще раз повторне сканування post
не має вартості запуску: воно починає виводити рядки негайно. Але сорт має значну вартість запуску, 23283.24
оскільки він повинен сортувати всю таблицю, перш ніж вона зможе вивести навіть один рядок . Загальна вартість сортування 23859.27
лише трохи вища, ніж вартість запуску, що відображає той факт, що після сортування всього набору даних, відсортовані дані можуть видаватися дуже швидко.
Зауважте, що час запуску пристрою LIMIT
23283.24
точно рівно часу запуску сортування. Це не тому, що LIMIT
сама по собі має високий час запуску. Він фактично має нульовий час запуску сам по собі, але EXPLAIN
згортає всі витрати дитини на кожного з батьків, тому час LIMIT
запуску включає суму часу запуску його дітей.
Такий набір витрат може ускладнити розуміння вартості виконання кожного окремого компонента. Наприклад, у нас LIMIT
нульовий час запуску, але це не очевидно на перший погляд. З цієї причини ще декілька людей, пов’язані з тлумаченням.depesz.com , - інструмент, створений Губертом Любачевським (він же depesz), який допомагає зрозуміти EXPLAIN
, серед іншого, - відняття дитячих витрат від батьківських витрат. У деяких коротких публікаціях про свій інструмент він згадує деякі інші складності .
Він виконується від більшості відступів до найменших відступів, і я вважаю, що знизу плану до верху. (Отже, якщо є два відступних розділи, перший, який знаходиться внизу сторінки, виконується спочатку, потім, коли вони зустрічаються з іншими, виконується правило, тоді виконується правило, яке приєднується до них.)
Ідея полягає в тому, що на кожному кроці є 1 або 2 набори даних, які надходять і обробляються за деяким правилом. Якщо лише один набір даних, ця операція робиться для цього набору даних. (Наприклад, скануйте індекс, щоб визначити, які рядки ви хочете, відфільтруйте набір даних або відсортуйте його.) Якщо два, два набори даних - це дві речі, які відступають далі, і до них приєднується правило, яке ви бачите. Про значення більшості правил можна легко здогадатися (особливо, якщо ви прочитали купу планів пояснення раніше), однак ви можете спробувати перевірити окремі пункти або заглянувши в документацію, або (простіше), просто вписавши фразу в Google разом із кількома ключовими словами EXPLAIN
.
Це, очевидно, не повне пояснення, але воно забезпечує достатній контекст, щоб ви могли зрозуміти, що хочете. Наприклад, розгляньте цей план із фактичної бази даних:
explain analyze
select a.attributeid, a.attributevalue, b.productid
from orderitemattribute a, orderitem b
where a.orderid = b.orderid
and a.attributeid = 'display-album'
and b.productid = 'ModernBook';
------------------------------------------------------------------------------------------------------------------------------------------------------------
Merge Join (cost=125379.14..125775.12 rows=3311 width=29) (actual time=841.478..841.478 rows=0 loops=1)
Merge Cond: (a.orderid = b.orderid)
-> Sort (cost=109737.32..109881.89 rows=57828 width=23) (actual time=736.163..774.475 rows=16815 loops=1)
Sort Key: a.orderid
Sort Method: quicksort Memory: 1695kB
-> Bitmap Heap Scan on orderitemattribute a (cost=1286.88..105163.27 rows=57828 width=23) (actual time=41.536..612.731 rows=16815 loops=1)
Recheck Cond: ((attributeid)::text = 'display-album'::text)
-> Bitmap Index Scan on (cost=0.00..1272.43 rows=57828 width=0) (actual time=25.033..25.033 rows=16815 loops=1)
Index Cond: ((attributeid)::text = 'display-album'::text)
-> Sort (cost=15641.81..15678.73 rows=14769 width=14) (actual time=14.471..16.898 rows=1109 loops=1)
Sort Key: b.orderid
Sort Method: quicksort Memory: 76kB
-> Bitmap Heap Scan on orderitem b (cost=310.96..14619.03 rows=14769 width=14) (actual time=1.865..8.480 rows=1114 loops=1)
Recheck Cond: ((productid)::text = 'ModernBook'::text)
-> Bitmap Index Scan on id_orderitem_productid (cost=0.00..307.27 rows=14769 width=0) (actual time=1.431..1.431 rows=1114 loops=1)
Index Cond: ((productid)::text = 'ModernBook'::text)
Total runtime: 842.134 ms
(17 rows)
Спробуйте прочитати це для себе і подивіться, чи є сенс.
Що я читав, це те, що база даних спочатку сканує id_orderitem_productid
індекс, використовуючи його, щоб знайти потрібні рядки orderitem
, потім сортує цей набір даних за допомогою quicksort (тип, який використовується, зміниться, якщо дані не вміщуються в оперативній пам'яті), а потім відкладає цю сторону.
Далі він сканує orditematt_attributeid_idx
знайти рядки, від яких хоче, orderitemattribute
а потім сортує цей набір даних за допомогою швидкості.
Потім він бере два набори даних і об'єднує їх. (Об'єднання злиття - це свого роду "блискавкова" операція, коли вона проводить паралельно два відсортовані набори даних, випромінюючи з'єднаний рядок, коли вони збігаються.)
Як я вже говорив, ви працюєте через внутрішню частину плану до зовнішньої частини, знизу вгору.
Існує також онлайн-інструмент для помічників Depesz , який підкреслить, де є дорогі частини результатів аналізу.
також є один, ось такі самі результати , які мені роблять зрозумілішими, де проблема.
PgAdmin покаже вам графічне зображення плану пояснення. Перемикання між цими двома можливостями допоможе вам зрозуміти, що означає текстове подання. Однак, якщо ви просто хочете дізнатися, що це відбувається, ви можете завжди завжди використовувати графічний інтерфейс.
Офіційна документація PostgreSQL надає цікаве, ретельне пояснення щодо розуміння результатів пояснення.