На какие ключевые метрики в выводе EXPLAIN ANALYZE (PostgreSQL) следует обращать внимание для поиска узких мест?

Ответ

EXPLAIN ANALYZE показывает реальный план выполнения запроса. Вот на что нужно смотреть в первую очередь:

1. Тип доступа (Scan Type):

  • Seq Scan (последовательное чтение) — чтение всей таблицы. Тревожный знак для больших таблиц. Ищи возможность добавить индекс.
  • Index Scan / Index Only Scan — использование индекса. Обычно эффективно.
  • Bitmap Heap Scan — компромисс, когда индекс возвращает много строк.

2. Соотношение оценок и реальных значений (Rows vs Actual Rows):

->  Seq Scan on large_table  (cost=0.00..100.00 rows=500 width=4)
                             (actual time=0.5..50.0 rows=100000 loops=1)

Планировщик ожидал 500 строк, а получил 100 000. Это указывает на устаревшую статистику. Нужно выполнить ANALYZE table_name;.

3. Эффективность фильтрации (Rows Removed by Filter):

Filter: (status = 'active')
Rows Removed by Filter: 98000

Индекс отфильтровал 98% строк. Если status = 'active' — частый запрос, нужен индекс CREATE INDEX ON table_name(status).

4. Стоимость и время (Cost & Actual Time):

  • cost=0.00..100.00 — оценка планировщика (стартовая..общая).
  • actual time=0.5..50.0 — реальное время в миллисекундах. Большое расхождение с cost — повод проверить статистику.

5. Алгоритмы соединения (Join Type):

  • Nested Loop — хорош для маленьких наборов данных.
  • Hash Join — эффективен для средних/больших таблиц, требует памяти.
  • Merge Join — лучший для больших отсортированных данных.

6. Использование диска (Temp Files, Buffers):

  • Temp Written: 1000 kB — запись на диск (часто при сортировке или хэш-соединениях). Увеличьте work_mem.
  • Shared Hit: 95% — высокий процент попаданий в кэш — хорошо.

Практический пример проблемы:

-- Запрос: SELECT * FROM orders WHERE created_at > '2023-01-01';
Seq Scan on orders  (cost=0.00..12500.00 rows=100 width=100)
                    (actual time=5.0..1200.0 rows=500000 loops=1)
  Filter: (created_at > '2023-01-01'::date)
  Rows Removed by Filter: 100000

Диагноз: Полный сканирование таблицы (Seq Scan), хотя условие WHERE отфильтровывает много строк. Решение: Создать индекс для ускорения поиска по дате: CREATE INDEX idx_orders_created_at ON orders(created_at);.

Ответ 18+ 🔞

А, вот и ты, мой друг, пришёл разобраться с этим EXPLAIN ANALYZE, который как рентген для твоих кривых запросов. Сейчас я тебе на пальцах, с примерами из жизни, объясню, куда смотреть, чтобы не сидеть потом с лицом кирпичом, глядя на тормоза.

1. Тип доступа — это как ты ищешь иголку в стоге сена.

  • Seq Scan (последовательное чтение) — это когда ты берёшь этот стог сена и методично, соломинка за соломинкой, перебираешь его весь. Для большой таблицы — это пиздец, а не план. Если видишь это — ищи, куда бы индекс воткнуть.
  • Index Scan / Index Only Scan — а вот это уже умно. Ты берёшь магнит (индекс) и быстро находишь эту иголку. Красота.
  • Bitmap Heap Scan — это такой хитрожопый компромисс. Магнит нашел дохуя иголок сразу, и ты потом кучкой их собираешь. Не идеально, но часто лучше, чем весь стог перетряхивать.

2. Соотношение «Ожидал vs Получил» — тут планировщик может обосраться. Смотри на строчку:

->  Seq Scan on large_table  (cost=0.00..100.00 rows=500 width=4)
                             (actual time=0.5..50.0 rows=100000 loops=1)

Видишь? Парень думал, что строк будет 500, а их, блядь, 100 000 вылезло! Это кричит тебе: «Э, сабака сука, статистика по таблице устарела!». Лечится командой ANALYZE table_name;. Сделай и не парься.

3. Эффективность фильтра — сколько мусора отсеяли.

Filter: (status = 'active')
Rows Removed by Filter: 98000

Вот смотри: из 100к строк 98к — говно, которое отфильтровали. Если ты постоянно ищешь active заказы, а индекса на status нет — ты просто конченый оптимизатор. Поставь индекс: CREATE INDEX ON table_name(status); и живи спокойно.

4. Стоимость и время — где планировщик пиздит как сивый мерин.

  • cost=0.00..100.00 — это его фантазии, «условные единицы».
  • actual time=0.5..50.0 — а это суровая правда жизни в миллисекундах. Если actual time в десятки раз больше, чем вторая цифра в cost — всё, пиздец, планировщик обосрался. Скорее всего, опять статистика кривая.

5. Алгоритмы соединения — как ты сводишь данные.

  • Nested Loop — как два вложенных цикла. Для мелких данных — ок, для крупных — медленная смерть.
  • Hash Join — быстрый парень для средних и больших таблиц. Но он, сука, жрёт оперативку, как не в себя.
  • Merge Join — аристократ. Любит, когда данные уже отсортированы. Для больших упорядоченных наборов — просто песня.

6. Использование диска — признак того, что память кончилась.

  • Temp Written: 1000 kB — видишь эту запись? Это твой запрос, бедолага, начал срать на диск, потому что в work_mem не влезло. Увеличивай work_mem в постгресе, и жизнь наладится.
  • Shared Hit: 95% — а вот это хороший знак. Почти всё из кэша взяли. Значит, часто используемые данные под рукой.

А теперь живой пример, где всё пошло по пизде:

Допустим, у тебя запрос: SELECT * FROM orders WHERE created_at > '2023-01-01'; А в плане такое:

Seq Scan on orders  (cost=0.00..12500.00 rows=100 width=100)
                    (actual time=5.0..1200.0 rows=500000 loops=1)
  Filter: (created_at > '2023-01-01'::date)
  Rows Removed by Filter: 100000

Диагноз: Полный, ебаный Seq Scan на таблице orders. Планировщик, тот ещё мудак, думал, что строк будет 100, а их полмиллиона. И он тупо читает всю таблицу, чтобы потом 100к строк выкинуть фильтром. Это пиздец, а не оптимизация.

Решение: Да ты чё, внатуре? По полю, по которому фильтруешь (created_at), индекса нет? Ну ты даёшь! Берёшь и создаёшь: CREATE INDEX idx_orders_created_at ON orders(created_at);. После этого Seq Scan скорее всего сменится на быстрый Index Scan, и запрос полетит, как угорелый.

Вот и вся магия. Смотри в план, думай головой, а не жопой, и будет тебе счастье.