Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как определить быстроту запроса
Оценка производительности запросов — критический навык для оптимизации приложения. Существует несколько подходов от простых до продвинутых.
1. Базовое измерение времени выполнения
// Простой способ: System.currentTimeMillis()
public List<User> findUsersSlow() {
long startTime = System.currentTimeMillis();
List<User> users = userRepository.findAll();
long duration = System.currentTimeMillis() - startTime;
log.info("Query took: {}ms", duration);
return users;
}
// Лучше: System.nanoTime() для более точного измерения
public List<User> findUsersFast() {
long startTime = System.nanoTime();
List<User> users = userRepository.findAll();
long durationNanos = System.nanoTime() - startTime;
long durationMillis = durationNanos / 1_000_000;
log.info("Query took: {}μs ({}ms)", durationNanos / 1_000, durationMillis);
return users;
}
2. Утилита для удобного измерения
public class QueryTimer {
private final long startTime = System.nanoTime();
private final String queryName;
public QueryTimer(String queryName) {
this.queryName = queryName;
}
public void logTime() {
long durationNanos = System.nanoTime() - startTime;
long millis = durationNanos / 1_000_000;
long micros = (durationNanos % 1_000_000) / 1_000;
if (millis > 0) {
log.info("[{}] {}ms", queryName, millis);
} else {
log.info("[{}] {}μs", queryName, micros);
}
}
public static void measure(String name, Runnable task) {
QueryTimer timer = new QueryTimer(name);
task.run();
timer.logTime();
}
}
// Использование
QueryTimer.measure("Find all users", () -> {
userRepository.findAll();
});
3. Аспект для автоматического логирования
@Component
@Aspect
public class QueryPerformanceAspect {
private static final long SLOW_QUERY_THRESHOLD = 500; // ms
private static final Logger log = LoggerFactory.getLogger(QueryPerformanceAspect.class);
@Around("execution(* com.example.repository..*.*(..))")
public Object logQueryPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.nanoTime();
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long durationNanos = System.nanoTime() - startTime;
long durationMs = durationNanos / 1_000_000;
if (durationMs > SLOW_QUERY_THRESHOLD) {
log.warn("SLOW QUERY: {}.{} took {}ms",
className, methodName, durationMs);
} else {
log.debug("{}.{} took {}ms",
className, methodName, durationMs);
}
}
}
}
4. Бенчмарки с JMH (Java Microbenchmark Harness)
// Зависимость в pom.xml
// <dependency>
// <groupId>org.openjdk.jmh</groupId>
// <artifactId>jmh-core</artifactId>
// <version>1.36</version>
// </dependency>
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@Fork(value = 1, warmups = 0)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 10)
public class QueryPerformanceBenchmark {
private UserRepository userRepository;
@Setup
public void setup() {
// Инициализация репозитория
}
@Benchmark
public List<User> findAllUsers() {
return userRepository.findAll();
}
@Benchmark
public List<User> findActiveUsers() {
return userRepository.findByStatus("ACTIVE");
}
@Benchmark
public User findUserById() {
return userRepository.findById(UUID.randomUUID()).orElse(null);
}
}
// Запуск: java -jar jmh-generated.jar
// Результат:
// Benchmark Mode Cnt Score Error Units
// QueryPerformanceBenchmark.findAllUsers avgt 5 150.234 ± 5.123 ms/op
// QueryPerformanceBenchmark.findActiveUsers avgt 5 45.891 ± 2.145 ms/op
// QueryPerformanceBenchmark.findUserById avgt 5 1.234 ± 0.089 ms/op
5. Профилирование с Micrometer
@Configuration
public class MetricsConfig {
@Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
@Service
public class MonitoredUserService {
private final UserRepository userRepository;
private final Timer findUsersTimer;
public MonitoredUserService(UserRepository userRepository,
MeterRegistry registry) {
this.userRepository = userRepository;
this.findUsersTimer = Timer.builder("query.users.find")
.description("Time to find all users")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
}
public List<User> findAllUsers() {
return findUsersTimer.recordCallable(() -> {
return userRepository.findAll();
});
}
// Или через @Timed
@Timed(value = "query.orders.find", description = "Find orders")
public List<Order> findOrders() {
return orderRepository.findAll();
}
}
// Метрики доступны через Prometheus
// GET /actuator/metrics/query.users.find
// {
// "name": "query.users.find",
// "measurements": [
// {"statistic": "COUNT", "value": 1000},
// {"statistic": "TOTAL_TIME", "value": 150000},
// {"statistic": "MEAN", "value": 150}
// ]
// }
6. Анализ Hibernate статистики
@Service
public class HibernateQueryAnalysis {
@PersistenceUnit
private EntityManagerFactory emf;
@Transactional(readOnly = true)
public void analyzeQueryStats() {
SessionFactory sf = emf.unwrap(SessionFactory.class);
Statistics stats = sf.getStatistics();
stats.clear();
// Выполни запрос
List<User> users = userRepository.findAll();
// Анализируй результаты
log.info("=== Hibernrate Statistics ===");
log.info("Entity load count: {}", stats.getEntityLoadCount());
log.info("Entity fetch count: {}", stats.getEntityFetchCount());
log.info("Query execution count: {}", stats.getQueryExecutionCount());
log.info("Query execution avg time: {}", stats.getQueryExecutionAvgTime());
log.info("Cache hit count: {}", stats.getCacheHitCount());
log.info("Cache miss count: {}", stats.getCacheMissCount());
// Детали по каждому запросу
for (String query : stats.getQueries()) {
log.info("Query: {}", query);
log.info(" Execution count: {}", stats.getQueryExecutionCount(query));
log.info(" Avg time: {}ms", stats.getQueryExecutionAvgTime(query));
}
}
}
7. JDBC логирование
# application.yml
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
use_sql_comments: true
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.springframework.jdbc.core: DEBUG
# Для более детального логирования времени
logging:
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
8. Query Profiler как Spring Bean
@Component
public class QueryProfiler {
private static final ThreadLocal<Map<String, Long>> queryTimes = ThreadLocal.withInitial(HashMap::new);
public void startQuery(String queryName) {
queryTimes.get().put(queryName, System.nanoTime());
}
public long endQuery(String queryName) {
long startTime = queryTimes.get().remove(queryName);
long durationNanos = System.nanoTime() - startTime;
return durationNanos / 1_000_000; // convert to millis
}
public void clear() {
queryTimes.remove();
}
}
@Service
public class ProfiledUserService {
private final QueryProfiler profiler;
public List<User> findUsers() {
profiler.startQuery("findAll");
try {
return userRepository.findAll();
} finally {
long duration = profiler.endQuery("findAll");
log.info("findAll took {}ms", duration);
}
}
}
9. Сравнение запросов (бенчмарк)
@Service
public class QueryComparisonBenchmark {
private final UserRepository userRepository;
public void compareQueryPerformance() {
// Запрос 1: БЕЗ индекса
long start1 = System.nanoTime();
List<User> users1 = userRepository.findByEmail("test@example.com");
long time1 = (System.nanoTime() - start1) / 1_000_000;
// Запрос 2: С индексом
long start2 = System.nanoTime();
List<User> users2 = userRepository.findByEmailIndexed("test@example.com");
long time2 = (System.nanoTime() - start2) / 1_000_000;
// Сравнение
double speedup = (double) time1 / time2;
log.info("Без индекса: {}ms, С индексом: {}ms, Ускорение: {}x",
time1, time2, String.format("%.2f", speedup));
}
}
10. Performance Testing Framework
public class PerformanceTester {
private static final int WARMUP_ITERATIONS = 1000;
private static final int TEST_ITERATIONS = 10000;
public PerformanceResult testQuery(String name, Supplier<?> query) {
// Warmup (JIT компиляция)
for (int i = 0; i < WARMUP_ITERATIONS; i++) {
query.get();
}
// Тест
long[] times = new long[TEST_ITERATIONS];
for (int i = 0; i < TEST_ITERATIONS; i++) {
long start = System.nanoTime();
query.get();
times[i] = System.nanoTime() - start;
}
return new PerformanceResult(
name,
Arrays.stream(times).min().orElse(0) / 1_000_000, // min
Arrays.stream(times).max().orElse(0) / 1_000_000, // max
Arrays.stream(times).average().orElse(0) / 1_000_000 // average
);
}
}
public class PerformanceResult {
public final String name;
public final long minMs;
public final long maxMs;
public final double avgMs;
public PerformanceResult(String name, long min, long max, double avg) {
this.name = name;
this.minMs = min;
this.maxMs = max;
this.avgMs = avg;
}
@Override
public String toString() {
return String.format("%s: min=%dms, max=%dms, avg=%.2fms",
name, minMs, maxMs, avgMs);
}
}
Метрики и пороги производительности
┌─ Отличная производительность
│ ├─ < 10ms — Simple queries (find by ID)
│ ├─ < 50ms — Complex queries with joins
│ ├─ < 200ms — Batch operations
│ └─ < 500ms — Full table scans
│
├─ Нужна оптимизация
│ ├─ 500ms - 2s — Медленные запросы
│ ├─ 2s - 10s — Очень медленные
│ └─ > 10s — Критически медленные
│
└─ Решения
├─ Добавить индексы
├─ Оптимизировать JOIN'ы
├─ Использовать кэширование
├─ Переписать запрос
└─ Распределить нагрузку
Измерение производительности — это постоянный процесс оптимизации приложения!