Как оптимизировать SQL запросы
Что проверяют
Умеете ли вы искать причину медленного запроса по плану выполнения, а не перечислять советы из статей. Следом аналитику часто дают план отчётного запроса или просят переписать запрос с тяжёлым JOIN.
Как отвечать
Начните с измерения. В PostgreSQL EXPLAIN (ANALYZE, BUFFERS) показывает реальный план: где Seq Scan по большой таблице, где оценка числа строк расходится с фактом в десятки раз, какой узел съедает время. Список самых тяжёлых запросов даёт pg_stat_statements.
Дальше типовые приёмы:
- условие под индекс:
created_at >= '2026-01-01' AND created_at < '2026-02-01'вместоdate_trunc('month', created_at) = .... Функция над колонкой не даёт использовать обычный индекс; - отсечение партиций: если таблица партиционирована по дате, фильтр по ключу партиции читает только нужные части;
- только нужные колонки: в колоночных базах вроде ClickHouse это главный рычаг, читаются только перечисленные столбцы;
- агрегация до соединения: сначала свернуть события до пользователя и дня, потом делать
JOIN, а не соединять сырые таблицы и схлопывать результат черезDISTINCT; EXISTSиUNION ALL: первое вместоJOINсDISTINCTдля проверки наличия, второе вместоUNION, если дублей быть не может;- оконные функции: вместо самосоединений и коррелированных подзапросов для задач вроде «последняя покупка клиента».
Если результат уходит в pandas, тяните из базы агрегаты, а не миллионы строк: GROUP BY на сервере быстрее цикла в Python. Большие выборки читайте частями через chunksize в pd.read_sql.
Где спотыкаются
Утверждают, что подзапросы всегда медленнее JOIN. Современные планировщики сами переписывают многие подзапросы, решает план, а не синтаксис. CTE в PostgreSQL начиная с 12 версии тоже встраиваются в запрос, если на них ссылаются один раз и не указано MATERIALIZED.
Считают, что LIMIT ускоряет любой запрос. При ORDER BY без подходящего индекса база всё равно обрабатывает все подходящие строки, прежде чем отдать первые десять.
Запускают EXPLAIN ANALYZE на UPDATE или DELETE вне транзакции. Он реально выполняет запрос, поэтому такие проверки делают между BEGIN и ROLLBACK.