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

Оптимизировал ли запросы

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

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

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

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

Оптимизировал ли запросы

Оптимизация запросов — это критически важная часть разработки, особенно в Java приложениях с БД.

Основные техники оптимизации SQL запросов

1. Индексы

Это первое и самое эффективное, что нужно сделать:

CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_orders_user_id_date ON orders(user_id, created_at);
  • Составные индексы учитывают порядок (для WHERE и ORDER BY)
  • UNIQUE индексы для уникальных полей (email, username)
  • Проверяй с EXPLAIN ANALYZE перед и после

2. EXPLAIN для анализа плана выполнения

EXPLAIN ANALYZE
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id;

Искай:

  • Seq Scan — плохо, нужен индекс
  • Index Scan — хорошо
  • Высокий cost — возможно, неправильный JOIN или фильтр

3. Избегай N+1 проблемы

❌ Плохо:

List<User> users = userRepository.findAll();
for (User user : users) {
    List<Order> orders = orderRepository.findByUserId(user.getId()); // N запросов
}

✅ Хорошо (Fetch JOIN):

@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE ...")
List<User> users = userRepository.findAllWithOrders();

✅ Хорошо (JOIN в SQL):

SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

4. Используй проекции вместо полных объектов

❌ Плохо — загружаешь 1000 полей:

List<User> users = userRepository.findAll(); // SELECT * FROM users

✅ Хорошо — только нужные поля:

List<UserDTO> users = userRepository.findAllNames(); // SELECT id, name FROM users

5. Пулинг соединений

В Java используй HikariCP (стандарт для Spring Boot):

spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000

Это дало мне 5-10x ускорение в реальных проектах.

6. Batch операции

❌ Плохо — INSERT по одному:

for (User user : users) {
    userRepository.save(user); // 1000 запросов
}

✅ Хорошо — batch вставка:

spring.jpa.properties.hibernate.jdbc.batch_size=20
userRepository.saveAll(users); // 50 запросов вместо 1000

7. Кеширование результатов

@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
    return userRepository.findById(id);
}

Или Redis для горячих данных:

String cachedUser = redisTemplate.opsForValue().get("user:" + id);

Инструменты для анализа

  • EXPLAIN ANALYZE — план выполнения SQL
  • Hibernate statistics — для отладки ORM:
    spring.jpa.properties.hibernate.generate_statistics=true
    
  • Spring Data Query Derivation — для быстрого создания запросов
  • Query Translator — визуализация SELECT'ов

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

До:

@GetMapping("/users/{id}/dashboard")
public Dashboard getDashboard(@PathVariable Long id) {
    User user = userRepository.findById(id); // 1 запрос
    List<Order> orders = orderRepository.findByUserId(id); // N+1
    List<Review> reviews = reviewRepository.findByUserId(id); // ещё N+1
    return new Dashboard(user, orders, reviews);
}

После:

@GetMapping("/users/{id}/dashboard")
public Dashboard getDashboard(@PathVariable Long id) {
    User user = userRepository.findByIdWithOrdersAndReviews(id); // 1 запрос с FETCH JOIN
    return new Dashboard(user, user.getOrders(), user.getReviews());
}

Чеклист оптимизации

  • ✅ Добавлены индексы на часто используемые поля
  • ✅ EXPLAIN для всех сложных запросов
  • ✅ Нет N+1 проблемы (FETCH JOIN или batch loading)
  • ✅ Пулинг соединений настроен
  • ✅ Batch операции для массивных INSERT/UPDATE
  • ✅ Кеширование горячих данных
  • ✅ Проекции вместо SELECT *
  • ✅ Нагрузочное тестирование (JMH, Gatling)