70. Оптимизировать частый запрос заказов пользователя
Условие задачи:
В системе часто выполняется запрос к таблице orders. При его выполнении наблюдается высокая нагрузка на диск.
Необходимо объяснить, как оптимизировать запрос и уменьшить количество операций чтения.
Код:
SELECT
status,
created_at
FROM orders
WHERE user_id = :userId
AND created_at >= now() - interval '30 days'
ORDER BY created_at DESC
LIMIT 20;
Спойлеры к решению
Подсказки
user_id, диапазон по created_at и сортировку по дате.💡 Здесь подходит составной индекс
(user_id, created_at DESC).💡 Благодаря
LIMIT 20 СУБД сможет остановить чтение после нахождения первых подходящих строк.💡 В PostgreSQL можно добавить
status через INCLUDE, чтобы создать покрывающий индекс.💡 Реальный эффект нужно проверить через
EXPLAIN (ANALYZE, BUFFERS).Решение
Основная оптимизация — создать составной индекс:
CREATE INDEX idx_orders_user_created_at
ON orders (user_id, created_at DESC);
Такой индекс хорошо соответствует запросу:
user_id = ... → точное условие
created_at >= ... → диапазон
ORDER BY created_at → порядок внутри user_id
LIMIT 20 → нужны только первые 20 строк
СУБД может быстро найти записи конкретного пользователя и читать их начиная с самых свежих, вместо сканирования большого количества строк и отдельной сортировки.
Для PostgreSQL можно сделать покрывающий индекс:
CREATE INDEX idx_orders_user_created_at_cover
ON orders (user_id, created_at DESC)
INCLUDE (status);
Все данные, необходимые запросу, тогда находятся в индексе:
user_id;created_at;status.
Это создаёт возможность для Index Only Scan, то есть чтения результата непосредственно из индекса без обращения к основной таблице.
Однако Index Only Scan не гарантирован: в PostgreSQL он также зависит от состояния visibility map и активности изменений в таблице.
Проверять результат оптимизации лучше так:
EXPLAIN (ANALYZE, BUFFERS)
SELECT
status,
created_at
FROM orders
WHERE user_id = :userId
AND created_at >= now() - interval '30 days'
ORDER BY created_at DESC
LIMIT 20;
Стоит обратить внимание на:
Index ScanилиIndex Only Scan;отсутствие лишней операции
Sort;количество прочитанных страниц (
shared read);фактическое количество обработанных строк;
время выполнения.
Если таблица очень большая, дополнительно можно рассмотреть партиционирование по created_at, особенно если большинство запросов работают только с ограниченным диапазоном дат. Но начинать оптимизацию следует именно с подходящего составного индекса и проверки плана выполнения.