← все задачи
SQL · задача 8 из 10
Почему запрос медленный
Продвинутый
20 минут
индексыEXPLAINsargable-условия
Условие
Дан запрос, который на проде выполняется двадцать секунд. Найдите проблемы и предложите исправления: SELECT * FROM orders WHERE DATE(created_at) = 2026-09-01 AND LOWER(status) = 'new' ORDER BY created_at DESC LIMIT 20;
Что требуется
- Назвать все причины медленной работы
- Переписать запрос
- Предложить индекс и объяснить порядок колонок в нём
Пример
-- было: 20 секунд, seq scan по 50 млн строк -- стало: 3 мс, index scan по 20 строкам
Сначала уточните
Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.
- Какова кардинальность status — много ли строк со значением new?
- Насколько велика таблица и как быстро растёт?
- Нужны ли действительно все колонки или хватит нескольких?
Показать решение Скрыть решение
Решение
-- Переписанный запрос: колонки «голые», условие по диапазону
SELECT id, customer_id, status, created_at, total
FROM orders
WHERE created_at >= DATE '2026-09-01'
AND created_at < DATE '2026-09-02'
AND status = 'new'
ORDER BY created_at DESC
LIMIT 20;
-- Индекс: сначала равенство, потом диапазон и сортировка
CREATE INDEX CONCURRENTLY orders_status_created_at_idx
ON orders (status, created_at DESC);
-- Проверяем, что планировщик им пользуется
EXPLAIN (ANALYZE, BUFFERS)
SELECT ...;
Почему так
Почему функция над колонкой убивает индекс
- DATE(created_at) и LOWER(status) — это вычисления над каждой строкой, обычный индекс к ним неприменим
- Планировщику приходится читать всю таблицу, чтобы посчитать функцию, — отсюда seq scan на 50 млн строк
- Лечится переписыванием на диапазон либо функциональным индексом по тому же выражению
Почему в индексе сначала status, потом created_at
- В составном индексе сначала идут колонки с равенством, потом та, по которой диапазон и сортировка
- При обратном порядке диапазон по дате «съест» индекс, и фильтр по статусу придётся применять к каждой найденной строке
- С порядком (status, created_at DESC) запрос берёт из индекса сразу двадцать нужных строк и не сортирует ничего
Чем плох SELECT *
- Лишние колонки — это лишние страницы с диска и лишний трафик, особенно если в таблице есть text или jsonb
- С узким списком колонок возможен index-only scan: ответ берётся из индекса, без похода в таблицу
- И отдельно: SELECT * ломается при изменении схемы, потому что клиент полагается на порядок колонок
Что смотреть в EXPLAIN
- ANALYZE даёт реальное время и число строк: разница между ожидаемым и фактическим показывает, что статистика устарела
- BUFFERS показывает, сколько страниц прочитано — именно чтение обычно и есть время запроса
- Ищем Seq Scan на большой таблице, Sort с внешней сортировкой на диск и вложенные циклы с большим числом итераций
Что спросят дальше
- Спросят, когда индекс не поможет: если условию удовлетворяет половина таблицы, seq scan честно быстрее
- Спросят про цену индексов: каждый замедляет вставку и занимает место, поэтому «добавить индекс на всё» — не ответ
- Спросят про CONCURRENTLY: обычный CREATE INDEX блокирует запись в таблицу на всё время построения
Следующая задача
Вставить или обновить без гонок — Счётчик, который увеличивают два процесса одновременно: классическая потерянная запись.