Чертёж без рейса: EXPLAIN
O que você vai aprender
- читать паспорт узла плана:
cost=старт..итог,rows,width - понимать, почему cost — попугаи, а не миллисекунды
- отличать «сколько строк узел прочитает» от «сколько отдаст наверх»
- видеть, как дерево плана растёт: над сканом появляется новый этаж
Второе утро в машинном отделении. Вчерашний чертёж зависшего отчёта так и висит на голопанели у люка — дерево из строк, испещрённое цифрами, которые ты тогда вежливо обошёл взглядом.
Сегодня ты разглядываешь приборную доску штурмана и замечаешь странное: на ней нет ни одних часов. Шкалы, счётчики, градуировки — а секунд нигде нет.
КВЕРИ: Верно подмечено. Штурман не носит часов: когда он выбирает маршрут, рейс ещё не начался — мерить нечего. Он меряет работу: сколько страниц перелистать, сколько строк перещупать. Единицы у этой работы условные — своей линейки у штурмана нет, поэтому курсанты зовут их попугаями: как удава в старом мультфильме, которого мерили попугаями за отсутствием метра. Попугай не переводится ни в секунды, ни в байты; он годится ровно для одного — сравнить два маршрута между собой. Дальше в курсе слово «попугаи» означает только это: условные единицы
cost.
Научимся читать эти шкалы. Открой любой план — и первое, что бросается в глаза, — это cost. С него и начнём.

Паспорт узла: три показания прибора
План — дерево узлов, и у каждого узла один и тот же формат паспорта:
Узел (cost=старт..итог rows=прогноз width=байты)
cost=старт..итог— цена узла в условных единицах, и чисел здесь ДВА. Startup — сколько работы нужно, чтобы выдать первую строку. Total — чтобы выдать последнюю. Зачем два — увидишь через несколько минут, когда на чертеже появится узел, у которого эти цифры почти совпадают.rows— сколько строк узел, по прогнозу, отдаст наверх. Прогноз, не факт: запрос не выполнялся.width— средняя ширина одной такой строки в байтах.rows × width— сколько данных потечёт вверх по дереву.
Про cost запомни главное: это попугаи, а не миллисекунды. Единица — не время, а условная работа: назначает цену каждому действию — перелистнуть страницу, проверить строку — и складывает. Цена нужна ему ровно для одного: сравнивать маршруты МЕЖДУ СОБОЙ и брать самый дешёвый. Перевести попугаев в секунды нельзя: на другом железе та же цена обернётся другим временем.
Дерево читаешь сверху вниз, а исполняется оно снизу вверх — это ты помнишь ещё из Хранилища. Значит, нижние этажи заняты чтением таблиц, а верхние — работой над уже добытыми строками; что при этом показывают их приборы, увидишь на втором чертеже.
Открой паспорт самого простого маршрута — фильтра по журналу:
Читаем показания
cost=0.00..3763.00. Startup — ноль: последовательный проход отдаёт первую подходящую строку сразу, копить ему нечего. Total — 3763 попугая за весь журнал. Из чего сложилась эта цена, разберём через пару уроков, когда откроем карту ввода-вывода; пока это просто ценник маршрута.
Filter строкой ниже. Фильтр — не отдельный узел: скан читает все сто восемьдесят тысяч строк подряд и наружу пропускает только warn. Поэтому rows — это не «сколько прочитает», а «сколько отдаст наверх».
rows. На чертеже — три с лишним тысячи; запусти ячейку ещё раз — и цифра почти наверняка сдвинется: от прогона к прогону она гуляет. Так и должно быть: не пересчитывал warn-строки — он заглянул в статистику, выборочную перепись архива, а песочница пересобирает журнал и перепись на каждый запуск, каждый раз опрашивая чуть другие строки. На самом деле warn-показаний в журнале ровно 3600: прогноз промахивается на пару сотен строк — такой точности для выбора маршрута более чем достаточно.
width=21. Средняя строка результата — 21 байт: ровно те четыре колонки, которые ты попросил. Попроси SELECT * — width вырастет, и вверх по дереву потечёт больше байтов.
КВЕРИ: Прогноз rows — самое важное показание на всей доске. Пока он честный, штурман выбирает хорошие маршруты. Когда врёт — начинаются сорок секунд. Запомни эту пару — прогноз и факт: мы к ней ещё вернёмся.
Теперь заставь штурмана достать второй механизм: попроси тот же список, но по порядку значений.
ORDER BY value — и дерево стало двухэтажным: над сканом вырос узел Sort. Сравни две пары cost: у скана startup 0.00, у Sort первая цифра почти равна второй.Дерево растёт
ORDER BY не изменил маршрут скана: та же цена 0.00..3763.00, тот же width=21. Прогноз rows снова чуть другой — но это всё та же гуляющая перепись из прошлой ячейки, а не эффект ORDER BY. Зато над сканом вырос новый этаж: Sort, со стрелкой -> перед сканом-ребёнком. Сверху написано «отсортируй то, что отдаст скан»; исполнение, как всегда, начинается снизу: скан добывает warn-строки, Sort их выстраивает.
Теперь посмотри на пару cost у Sort — в ней вся суть двух чисел. Первая цифра почти равна второй: без малого четыре тысячи попугаев ещё ДО первой выданной строки. Сортировка не может отдать наверх даже одну строку, пока не проглотит все: вдруг последняя строка скана окажется наименьшей. Скан — узел-ручей, строки текут сразу; Sort — узел-плотина, он копит. Пара старт..итог и говорит тебе, ручей перед тобой или плотина.
И ещё одна привычка чтения: цена узла включает цену его детей. Из четырёх тысяч попугаев Sort бóльшую часть — 3763 — унаследовал от скана; собственная работа сортировки стоит немного: три с половиной тысячи строк — не тот объём, на котором она потеет.
Чертёж ты теперь читаешь. Но помни: машина пока не прочитала по-настоящему ни одной страницы журнала — всё это прогнозы штурмана. В следующем уроке отправим запрос в настоящий рейс — EXPLAIN ANALYZE — и на приборной доске впервые появится секундомер: оценка встретится с фактом.
Seq Scan on ls_readings (cost=0.00..3763.00 rows=3540 width=21). Что означает вторая цифра пары cost — 3763.00?cost=0.00..3763.00 и объявляет: «3763 — значит, запрос идёт примерно 3,7 секунды». Прав ли он?