← Назад к вопросам
Что делать если запрос стал отрабатывать менее эффективно
1.7 Middle🔥 231 комментариев
#REST API и микросервисы#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Диагностика и оптимизация неэффективных запросов
Когда запрос, который работал быстро, внезапно замедляется — это признак проблемы в коде, БД или инфраструктуре. Рассмотрю методическое решение этой проблемы.
Шаг 1: Проверить логи и мониторинг
// Добавляем логирование времени выполнения
@Service
public class UserService {
@Autowired
private UserRepository repository;
@Autowired
private LogService logService;
public List<User> getAllUsers() {
long startTime = System.currentTimeMillis();
try {
List<User> users = repository.findAll();
long duration = System.currentTimeMillis() - startTime;
logService.log("getAllUsers took " + duration + "ms");
if (duration > 1000) { // Если больше секунды
logService.warn("getAllUsers is slow: " + duration + "ms");
}
return users;
} catch (Exception e) {
logService.error("Error in getAllUsers", e);
throw e;
}
}
}
Шаг 2: Профилировать запрос
На уровне БД
-- PostgreSQL: EXPLAIN ANALYZE
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 123;
-- Результат показывает:
-- Seq Scan on orders (sequential scan = без индекса)
-- Rows: 10000 (сканирует все 10000 строк)
-- Planning time: 0.2ms
-- Execution time: 45.2ms
-- Решение: создать индекс
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
-- Повторный запрос:
-- Index Scan using idx_orders_customer_id
-- Rows: 50
-- Execution time: 0.8ms (55x быстрее!)
На уровне JPA/Hibernate
// Включаем SQL логирование
// application.properties
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
// Пример вывода:
// SELECT u.* FROM users u WHERE u.id = ?
// Binding: [1]
// Или используем Hibernate statistics
@Configuration
public class HibernateConfig {
@Bean
public StatisticsFilter statisticsFilter() {
return new StatisticsFilter();
}
}
Шаг 3: Распространённые причины деградации
Причина 1: N+1 запросы
// ПЛОХО: вызовет N+1 запрос
@Entity
public class Customer {
@OneToMany
private List<Order> orders; // Без FetchType.LAZY
}
// Код:
List<Customer> customers = repository.findAll();
for (Customer c : customers) {
System.out.println(c.getOrders()); // Каждая итерация = отдельный SQL!
// 1 запрос findAll() + N запросов для каждого getOrders()
}
// ХОРОШО: FetchType.LAZY + явная загрузка
@Entity
public class Customer {
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;
}
// Или с JOIN FETCH
public interface CustomerRepository extends JpaRepository<Customer, Long> {
@Query("SELECT DISTINCT c FROM Customer c LEFT JOIN FETCH c.orders")
List<Customer> findAllWithOrders();
}
// Или с @EntityGraph
public interface CustomerRepository extends JpaRepository<Customer, Long> {
@EntityGraph(attributePaths = "orders")
List<Customer> findAll();
}
Причина 2: Отсутствие индексов
-- До оптимизации
SELECT * FROM transactions
WHERE created_at > '2024-01-01' AND status = 'pending';
-- Time: 3000ms (сканирует 1 млн строк)
-- После оптимизации
CREATE INDEX idx_transactions_created_status
ON transactions(created_at, status);
-- Time: 15ms (использует индекс, сканирует 500 строк)
Причина 3: Рост объёма данных
Олучше:
- 1000 строк: запрос 10ms
- 10000 строк: запрос 50ms (линейный рост)
Плохо:
- 1000 строк: запрос 10ms
- 10000 строк: запрос 500ms (квадратичный рост)
→ Нет индекса или неэффективный алгоритм
Причина 4: Memory leak в кэше
// ПЛОХО: кэш растёт бесконечно
@Service
public class CachingService {
private Map<Long, User> userCache = new HashMap<>();
public User getUser(Long id) {
if (!userCache.containsKey(id)) {
User user = repository.findById(id).orElse(null);
userCache.put(id, user); // Растёт бесконечно!
}
return userCache.get(id);
}
}
// ХОРОШО: с ограничением размера
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users");
}
}
// Или с Spring Cache
@Service
public class UserService {
@Cacheable(value = "users", unless = "#result == null")
public User getUser(Long id) {
return repository.findById(id).orElse(null);
}
}
Причина 5: Неправильная конфигурация БД
# application.yml
spring:
jpa:
properties:
hibernate:
jdbc.batch_size: 20 # Батчинг
order_inserts: true
order_updates: true
generate_statistics: true
datasource:
hikari:
maximum-pool-size: 10 # Пул соединений
connection-timeout: 20000
Шаг 4: Инструменты для диагностики
1. JVM Profiler
# JProfiler, YourKit
# Показывает: какой метод занимает сколько времени
# Или встроенный jstat
jstat -gc <pid> 1000 # Смотрим GC каждую секунду
# Если много GC паузы → есть утечка памяти или неэффективное выделение
2. DataSource Profiler
<!-- pom.xml -->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.16</version>
</dependency>
<!-- application.properties -->
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:postgresql://localhost:5432/mydb
<!-- Теперь видим все SQL с временем выполнения -->
3. APM решения
// New Relic, DataDog, Elastic APM
// Показывают: какие запросы медленные, где узкие места
// Например, Micrometer
@Configuration
public class MetricsConfig {
@Bean
public MeterFilter apiHttpRequestsTimer() {
return new MeterFilter() {
@Override
public void configure(Meter.Id id, MeterConfig config) {
if (id.getName().startsWith("http.server.requests")) {
config.tag("endpoint", id.getTag("uri"));
}
}
};
}
}
Методический процесс оптимизации
Шаг 1: Измерь
Сколько времени занимает запрос?
Можно использовать: Stopwatch, System.currentTimeMillis(), @Timed
Шаг 2: Профилируй
Где тратится время?
- SQL выполнение?
- Маршаллинг/сериализация?
- Сетевая задержка?
- Обработка в Java?
Шаг 3: Оптимизируй
Вариант 1: Оптимизировать SQL
- Добавить индекс
- Переписать запрос
- Кэшировать результаты
Вариант 2: Оптимизировать Java код
- Избежать N+1
- Использовать connection pooling
- Параллелизовать
Вариант 3: Архитектурные изменения
- Разделить монолит
- Добавить кэширующий слой
- Использовать message queue
Шаг 4: Проверь
Повторные измерения:
- До оптимизации: 1000ms
- После оптимизации: 100ms
- Улучшение: 10x
Пример полной диагностики
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/orders")
public ResponseEntity<List<Order>> getAllOrders() {
Stopwatch sw = Stopwatch.createStarted();
List<Order> orders = orderService.getAllOrders();
long duration = sw.stop().elapsed(TimeUnit.MILLISECONDS);
if (duration > 100) {
logger.warn("getAllOrders took {}ms", duration);
}
return ResponseEntity.ok(orders);
}
}
@Service
public class OrderService {
@Autowired
private OrderRepository repository;
// С EXPLAIN ANALYZE видим, что SQL медленный
// Проблема: нет индекса по status
public List<Order> getAllOrders() {
return repository.findByStatus("PENDING");
}
}
// Решение:
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query(value = "SELECT * FROM orders WHERE status = ?1 ORDER BY created_at DESC",
nativeQuery = true)
List<Order> findByStatus(String status);
}
// И в миграции:
CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
Чеклист оптимизации
- Включил SQL логирование
- Запустил EXPLAIN ANALYZE
- Проверил индексы на столбцах из WHERE
- Проверил N+1 запросы
- Проверил размер результата (не загружаю лишние поля)
- Добавил кэширование если нужно
- Профилировал JVM (нет ли memory leak)
- Проверил пул соединений БД
- Пересчитал метрики после оптимизации
Вывод
Когда запрос замедляется — это НЕ магия. Всегда есть конкретная причина:
- Добавилось данных → нужен индекс
- N+1 запросы → нужна JOIN FETCH
- Memory leak → нужен профайлер
- Неправильный SQL → нужен EXPLAIN
- Сетевая задержка → нужна асинхронность
Методический подход (измерь → профилируй → оптимизируй → проверь) всегда даст результат.