EXPLAIN и оптимизация: вопросы для собеседования (часть 4)
EXPLAIN показывает план выполнения запроса и помогает понять, где теряется производительность. На собеседованиях спрашивают про индексы, Seq Scan vs Index Scan, стоимость JOIN-ов и как переписать медленный запрос. Этот навык отличает аналитика, который просто пишет SQL, от того, кто понимает, как база данных его выполняет.
Вопросы 16–20 из 20
16В таблице `orders` есть индекс по `status`, но `EXPLAIN` для условия `WHERE status IN ('paid','shipped')` показывает `Seq Scan`. Какое объяснение наиболее правдоподобно, если эти статусы встречаются у большинства заказов?
AПланировщик ожидает, что нужно прочитать слишком много строк, и `Seq Scan` будет дешевле, чем множество обращений к индексу.
BИндекс всегда игнорируется, если в условии используется `IN`.
C`Seq Scan` означает, что таблица `orders` заблокирована другим запросом.
DЭто гарантированный признак повреждения индекса.
Ответ: Индекс не всегда выгоден, когда отбирается большая часть таблицы.
Индекс помогает, когда условие `WHERE` сильно сужает выборку. Если фильтр пропускает большую долю строк, чтение через индекс может превратиться в множество «случайных» чтений таблицы и быть дороже. Тогда планировщик выбирает `Seq Scan`, и это может быть нормальным.
17Есть индекс по `orders.created_at`, но `EXPLAIN` для фильтра `WHERE date(created_at) = current_date` показывает `Seq Scan`. Почему это часто происходит?
AИндекс «ломается», если использовать `WHERE`.
BФункция `date(created_at)` меняет выражение, и индекс по `created_at` не может быть использован напрямую.
C`EXPLAIN` всегда показывает `Seq Scan`, даже если индекс используется.
DУсловие `=` отключает использование индексов.
Ответ: Индекс чаще используется, когда в `WHERE` сравнивается сам столбец, а не результат функции от него.
Когда вы пишете `date(created_at)`, база должна вычислить дату для каждой строки, и обычный индекс по `created_at` становится мало полезен. Часто запрос переписывают в виде диапазона по времени, например `created_at >= current_date AND created_at < current_date + interval '1 day'`. Тогда условие становится «индексируемым», и шанс увидеть `Index Scan` выше.
18Вы добавили индекс по `created_at`, ожидая ускорить `SELECT * FROM orders ORDER BY created_at DESC LIMIT 50`, но `EXPLAIN` всё равно показывает `Seq Scan` и `Sort`. Какой вывод самый безопасный?
AВ текущих данных/условиях планировщик считает этот путь дешевле: индекс может не подходить по форме запроса или сортировка всё равно остаётся; нужно проверять селективность, статистику и конкретный план, а не делать вывод «индекс не работает вообще».
BПланировщик Postgres игнорирует индексы по `created_at`, если в запросе нет условия `WHERE` — без фильтрации индекс по умолчанию отключается.
CИндекс по `created_at` применяется только для `INSERT` и `UPDATE`, но не для `SELECT`, поэтому `EXPLAIN` правомерно показывает `Seq Scan`.
DНаличие `LIMIT 50` в запросе всегда блокирует использование любых индексов, и планировщик автоматически переключается на `Seq Scan`.
Ответ: То, что индекс не выбран, не означает, что он бесполезен — это решение планировщика в конкретных условиях.
Планировщик сравнивает альтернативы и выбирает то, что кажется дешевле по оценкам `cost`/`rows`. Индекс мог не подойти из‑за формы запроса, из‑за того, что нужно прочитать слишком много строк, или из‑за неактуальной статистики. `EXPLAIN` помогает увидеть факт: какой план выбран и где узлы `Sort`/`Seq Scan`, — а дальше уже искать причину и варианты переписывания запроса.
19Вы ожидаете, что `LIMIT 10` всегда означает «обработать только 10 строк». Но `EXPLAIN` для запроса `SELECT * FROM events WHERE user_id = 42 ORDER BY created_at DESC LIMIT 10` показывает `Sort` по большому количеству строк перед `Limit`. Что это означает?
AЧто `LIMIT 10` игнорируется и база вернёт все строки.
BЧто база обязана сначала вернуть все строки клиенту, а потом применить `LIMIT 10` на стороне приложения.
CЧто `EXPLAIN` запрещено использовать вместе с `ORDER BY`.
DЧто чтобы выбрать первые строки в нужном порядке, базе может понадобиться обработать и упорядочить большой набор данных, если нет подходящего индекса/плана; `LIMIT` не всегда делает запрос «дешёвым».
Ответ: `LIMIT` экономит работу только если план может рано «остановиться» (например, через индекс под `ORDER BY`).
Когда план включает `Sort`, базе всё равно нужно рассмотреть большой набор строк, чтобы понять, какие из них первые по `ORDER BY`. В этом случае `LIMIT` лишь сокращает финальный вывод, но не избавляет от основной работы. Подходящий индекс или более селективный `WHERE` позволяют плану рано остановиться и сделать `LIMIT` реально полезным.
20Вы строите виджет «последние события» и используете `ORDER BY created_at DESC LIMIT 100`. В `created_at` много одинаковых значений, и пользователи жалуются, что список «дрожит». Какое изменение лучше всего повышает стабильность результата, не ломая идею оптимизации через индекс?
AУбрать `ORDER BY`, оставив только `LIMIT 100`.
BЗаменить `LIMIT 100` на `OFFSET 100`.
CДобавить `DISTINCT` ко всем колонкам, чтобы порядок закрепился.
DСделать порядок детерминированным, например `ORDER BY created_at DESC, event_id DESC LIMIT 100` (и при необходимости иметь индекс, который поддерживает такой порядок).
Ответ: Для стабильного `LIMIT` нужен детерминированный `ORDER BY` с tie-breaker.
Если сортировка идёт только по `created_at`, то строки с одинаковым временем могут возвращаться в разном порядке в разных запусках — SQL это не запрещает. Добавление второго ключа (например, `event_id`) делает порядок однозначным. При хорошем индексе по `(created_at, event_id)` или похожем порядке оптимизация через чтение из индекса может сохраниться.
Хотите тренировать интерактивно?
В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать в Telegram