Ответ
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, и запрос полетит, как угорелый.
Вот и вся магия. Смотри в план, думай головой, а не жопой, и будет тебе счастье.