← Назад к вопросам

Что будешь делать с медленным SQL-запросом

2.0 Middle🔥 131 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оптимизация медленного SQL-запроса: систематический подход

Это очень важный практический вопрос! Я имею структурированный методологический подход к диагностике и оптимизации медленных запросов.

Мой пошаговый подход

Шаг 1: Диагностика

Сначала измеряю время выполнения и использую инструменты:

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    public List<Order> getActiveOrders() {
        long startTime = System.currentTimeMillis();
        List<Order> orders = orderRepository.findActiveOrdersWithCustomer();
        long duration = System.currentTimeMillis() - startTime;
        logger.info("Query took: {} ms", duration);
        return orders;
    }
}

Инструменты для профилирования:

  • EXPLAIN PLAN (PostgreSQL/MySQL)
  • Hibernate query logging
  • Slow query logs
  • Spring Boot Actuator metrics

Шаг 2: Анализ плана запроса

Смотрю на:

  • Sequential vs Index Scan
  • Количество прочитанных строк
  • Join методы
  • Стоимость операции

Шаг 3: Типовые проблемы и решения

Проблема 1: N+1 queries (самая частая!)

// ПЛОХО: N+1
List<Order> orders = orderRepository.findAll();
orders.forEach(order -> {
    order.getCustomer().getName(); // +N queries
});

// ХОРОШО: JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findActiveOrdersWithCustomer();

Проблема 2: Отсутствие индексов

-- МЕДЛЕННО: full scan
SELECT * FROM orders WHERE status = 'ACTIVE' AND created_at > ?;

-- БЫСТРО: с индексом
CREATE INDEX idx_orders_status_created ON orders(status, created_at);

Проблема 3: Большие JOIN'ы

-- ПЛОХО: много дублирования
SELECT * FROM orders o JOIN customers c JOIN items i;

-- ХОРОШО: только нужные поля
SELECT o.id, o.amount, c.name, COUNT(i.id)
FROM orders o
JOIN customers c ON o.customer_id = c.id
LEFT JOIN items i ON o.id = i.order_id
GROUP BY o.id;

Шаг 4: Оптимизационные техники

Техника 1: Pagination

Page<Order> page = orderRepository.findAll(
    PageRequest.of(0, 100, Sort.by("created_at"))
);

Техника 2: Caching

@Cacheable("orders-by-status")
public List<Order> getActiveOrders() {
    return orderRepository.findActiveOrdersWithCustomer();
}

Техника 3: DTO Queries

@Query("SELECT new com.example.OrderDTO(o.id, o.amount, c.name) " +
       "FROM Order o JOIN o.customer c")
List<OrderDTO> findOrderDTOs();

Техника 4: Индексы в БД

CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_customer_date ON orders(customer_id, created_at DESC);
ANALYZE TABLE orders;

Реальный пример оптимизации

// БЫЛО: 5 секунд
List<Order> orders = orderRepository.findByCreatedDateBetween(
    month.atStartOfDay(), month.plusMonths(1).atStartOfDay()
); // Нет индекса
orders.forEach(o -> o.getCustomer().getName()); // N+1

// СТАЛО: ~50ms
@Query("SELECT new com.example.OrderReportDTO(" +
       "o.id, o.amount, c.name, COUNT(i.id)) " +
       "FROM Order o " +
       "JOIN o.customer c " +
       "LEFT JOIN o.items i " +
       "WHERE o.created_at >= :startDate " +
       "GROUP BY o.id")
List<OrderReportDTO> findMonthlyReport(
    @Param("startDate") LocalDateTime startDate,
    @Param("endDate") LocalDateTime endDate
);

Мой контрольный список

  1. EXPLAIN PLAN - смотрю на план запроса
  2. Индексы - правильные индексы это 90% оптимизации
  3. N+1 queries - JOIN FETCH или отдельные запросы
  4. Pagination - для больших результатов
  5. Caching - для часто используемых данных
  6. Monitoring - постоянно отслеживаю performance
  7. Load testing - тестирую под реальной нагрузкой
  8. Communication - обсуждаю с DBA

Итог

Мой подход систематический: измеряю → профилирую → анализирую → оптимизирую → мониторю. Каждый запрос требует анализа.