70. Оптимизировать часто выполняемый запрос к таблице orders
Условие задачи:
📌 Есть запрос, который часто выполняется в системе. При его выполнении наблюдается высокая нагрузка на диск.
Нужно объяснить, что можно сделать для оптимизации такого запроса и снижения disk I/O.
Код:
SELECT status, created_at
FROM orders
WHERE user_id = :userId
AND created_at >= now() - interval '30 days'
ORDER BY created_at DESC
LIMIT 20;
Спойлеры к решению
Подсказки
WHERE + ORDER BY.💡 Для такого запроса обычно нужен составной индекс по
user_id и created_at.💡 Если СУБД поддерживает covering index / include columns, можно добавить
status, чтобы уменьшить чтение таблицы.💡
EXPLAIN ANALYZE поможет проверить, идёт ли full scan, bitmap scan или index scan.💡 Если таблица очень большая, можно подумать о партиционировании по дате.
Решение
Основное, что здесь нужно сделать — дать СУБД индекс, который подходит под фильтрацию и сортировку ✅
Для такого запроса хороший базовый вариант:
CREATE INDEX idx_orders_user_created_at
ON orders (user_id, created_at DESC);
Почему именно так:
user_id = :userId— точное условиеcreated_at >= ...— диапазонORDER BY created_at DESC— можно поддержать тем же индексомLIMIT 20— СУБД сможет быстро взять только верхние записи, а не читать много лишнего
Если СУБД поддерживает покрывающий индекс, можно ещё улучшить запрос и уменьшить чтение таблицы:
CREATE INDEX idx_orders_user_created_at_cover
ON orders (user_id, created_at DESC)
INCLUDE (status);
Тогда возможен index only scan, и нагрузка на диск станет меньше, потому что status не придётся дочитывать из основной таблицы.
Что ещё стоит сделать:
проверить план через
EXPLAIN ANALYZEубедиться, что запрос реально использует индекс
если таблица очень большая и запросы часто идут по последним 30 дням — рассмотреть партиционирование по
created_atследить за тем, чтобы статистика таблицы была актуальной (
ANALYZE)если данных очень много, можно подумать о кешировании на уровне приложения
Что обычно отвечать на собеседовании 🎯
Для такого запроса в первую очередь нужен составной индекс по
(user_id, created_at DESC), потому что он покрывает фильтрацию, сортировку иLIMIT.
Если хочется ещё сильнее снизить disk I/O, можно сделать покрывающий индекс сstatus, чтобы получить index only scan.
Дальше я бы проверил план черезEXPLAIN ANALYZEи при очень больших объёмах рассмотрел партиционирование по дате.