Как вы подходите к диагностике и оптимизации SQL-запроса, производительность которого деградировала?

«Как вы подходите к диагностике и оптимизации SQL-запроса, производительность которого деградировала?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Диагностика следует системному подходу: от анализа плана выполнения к поиску узких мест и внедрению решений.

Пошаговый план оптимизации:

1. Анализ текущего плана выполнения: Первым делом необходимо понять, как СУБД выполняет запрос, используя EXPLAIN ANALYZE. Это покажет:

  • Тип доступа к таблице: Seq Scan (полное сканирование) vs Index Scan.
  • Стоимость (cost) и время выполнения.
  • Порядок JOIN'ов.
  • Наличие операций, "съедающих" время: Sorts, Hash Aggregates, Filter.
-- Для PostgreSQL
EXPLAIN ANALYZE 
SELECT u.id, u.name, o.total 
FROM users u 
JOIN orders o ON u.id = o.user_id 
WHERE u.status = 'ACTIVE' AND o.created_at > NOW() - INTERVAL '30 days';

2. Поиск типовых причин деградации:

  • Отсутствие или неиспользование индекса: Запрос перестал использовать индекс из-за изменения условий (WHERE status = ? может не использовать индекс, если status имеет низкую селективность).
  • Некорректная оценка кардинальности: Оптимизатор неправильно оценил количество строк, выбрав неоптимальный план (например, Hash Join вместо Nested Loop).
  • Блокировки (Locks): Другие транзакции блокируют нужные строки или таблицы.
  • Рост объема данных: Запрос, работавший быстро на 1000 строк, может тормозить на 10 миллионах.
  • Проблемы со статистикой: Устаревшая статистика по таблицам ведет к плохим планам. Решение: ANALYZE table_name;.

3. Применение методов оптимизации:

  • Добавление/корректировка индексов:
    -- Создание составного индекса, покрывающего условия WHERE и ORDER BY
    CREATE INDEX idx_user_active_created ON users(status, created_at) 
    WHERE status = 'ACTIVE';
  • Рефакторинг запроса:
    • Убрать избыточные колонки в SELECT *.
    • Избегать функций в условиях WHERE (например, WHERE YEAR(created_at) = 2023), которые мешают использованию индексов.
    • Разбить один сложный запрос на несколько.
    • Использовать LIMIT и эффективную пагинацию (на основе ключа, а не OFFSET).
  • Оптимизация на уровне приложения:
    • Внедрение кэширования результатов запросов (Redis, Memcached).
    • Устранение проблемы N+1 в ORM (использовать JOIN FETCH в Hibernate).
    • Настройка пула соединений (HikariCP: maximumPoolSize, connectionTimeout).

4. Валидация изменений: После внесения правлений (индекс, рефакторинг) обязательно повторно запустить EXPLAIN ANALYZE и проверить запрос на тестовых данных, аналогичных production.