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

Как находить нагруженные SQL запросы в БД?

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

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

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

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

Поиск нагруженных SQL запросов в БД

Находить медленные запросы критически важно для оптимизации производительности приложения. Покажу несколько подходов для разных БД.

PostgreSQL: Query Performance Insights

1. Просмотр всех выполненных запросов

-- Включаем логирование медленных запросов
ALTER SYSTEM SET log_min_duration_statement = 1000; -- логируем запросы > 1сек
SELECT pg_reload_conf();

2. Встроенный view pg_stat_statements

-- Установить расширение (один раз)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Топ 10 самых медленных запросов (по total_time)
SELECT
    query,
    calls,
    total_time,
    mean_time,
    max_time,
    rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

3. Запросы, которые больше всего читают данных

SELECT
    query,
    calls,
    shared_blks_hit,      -- попадания в кэш
    shared_blks_read,     -- чтения с диска
    shared_blks_hit + shared_blks_read as total_reads,
    ROUND(100.0 * shared_blks_hit / NULLIF(shared_blks_hit + shared_blks_read, 0), 2) as cache_hit_ratio
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC
LIMIT 10;

4. Анализ плана выполнения (EXPLAIN)

-- Простой план
EXPLAIN
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY order_count DESC;

-- Подробный план с execution statistics
EXPLAIN ANALYZE
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY order_count DESC;

-- JSON формат для программной обработки
EXPLAIN (FORMAT JSON, ANALYZE)
SELECT * FROM users WHERE created_at > NOW() - INTERVAL '7 days';

В выводе смотрим на:

  • Seq Scan — полное сканирование таблицы (медленно)
  • Index Scan — использование индекса (быстро)
  • Nested Loop — вложенные циклы (может быть медленно)
  • Actual rows — сколько реально вернулось строк

MySQL/MariaDB: Query Performance

1. Slow Query Log

-- Включаем логирование медленных запросов
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- логируем запросы > 1сек
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- Проверяем расположение лога
SHOW VARIABLES LIKE 'slow_query_log_file';

2. Performance Schema

-- Включаем мониторинг
UPDATE setup_instruments 
SET ENABLED = 'YES', TIMED = 'YES' 
WHERE NAME LIKE 'statement/%';

-- Топ медленных запросов
SELECT
    event_name,
    COUNT_STAR as count,
    SUM_TIMER_WAIT / 1000000000000 as total_time_sec,
    AVG_TIMER_WAIT / 1000000000 as avg_time_ms
FROM events_statements_summary_global_by_event_name
WHERE EVENT_NAME LIKE 'statement/sql/%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

Мониторинг в Python коде

1. Логирование query execution time

import time
from functools import wraps
import logging

logger = logging.getLogger(__name__)

def log_slow_queries(threshold_ms: float = 100):
    """Декоратор для логирования медленных запросов"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed_ms = (time.perf_counter() - start) * 1000
            
            if elapsed_ms > threshold_ms:
                logger.warning(
                    f"Slow query: {func.__name__} took {elapsed_ms:.2f}ms",
                    extra={"query_time_ms": elapsed_ms}
                )
            return result
        return wrapper
    return decorator

@log_slow_queries(threshold_ms=50)
def get_user_with_orders(user_id: int):
    return db.query(User).filter(User.id == user_id).first()

2. SQLAlchemy query profiling

from sqlalchemy import event, create_engine
from sqlalchemy.engine import Engine
import time

engine = create_engine(
    "postgresql://user:pass@localhost/db",
    echo=False
)

@event.listens_for(Engine, "before_cursor_execute")
def receive_before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
    conn.info.setdefault("query_start_time", []).append(time.perf_counter())

@event.listens_for(Engine, "after_cursor_execute")
def receive_after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
    total = time.perf_counter() - conn.info["query_start_time"].pop(-1)
    
    if total > 0.1:  # логируем запросы > 100ms
        logger.warning(
            f"Slow SQL: {statement[:100]}... Took {total:.3f}s"
        )

3. Django ORM query logging

from django.db import connection, reset_queries
from django.conf import settings
from django.test.utils import CaptureQueriesContext

# Для development - видеть все запросы
if settings.DEBUG:
    from django.db import connection
    print(f"Executed {len(connection.queries)} queries")
    for query in connection.queries:
        print(f"Time: {query['time']}, SQL: {query['sql'][:100]}")

# Для тестов - проверить количество запросов
from django.test import TestCase

class UserViewTests(TestCase):
    def test_no_n_plus_one(self):
        with CaptureQueriesContext(connection) as context:
            users = list(User.objects.select_related('profile').all())
        
        # Проверяем что запросов разумное количество
        assert len(context.captured_queries) < 5

Инструменты мониторинга

1. pgAdmin

# Веб-интерфейс для PostgreSQL
# Доступен через: http://localhost:5050
# Можно видеть:
# - Slow queries
# - Table sizes
# - Index usage
# - Connection statistics

2. pg_stat_kcache (для Linux)

# Расширение для анализа кэша ОС
sudo apt-get install postgresql-contrib

pgsql> CREATE EXTENSION IF NOT EXISTS pg_stat_kcache;

# Показывает real I/O (не только shared buffers)
SELECT 
    query,
    reads,
    writes,
    blk_read_time,
    blk_write_time
FROM pg_stat_kcache()
WHERE reads > 0 OR writes > 0
ORDER BY reads DESC;

3. pgBadger (для анализа логов)

# Анализирует slow query log и выдаёт отчёт
pgbadger /var/log/postgresql/postgresql.log -o report.html

# Выводит:
# - Top slow queries
# - Most frequent queries
# - Hourly statistics

Типичные проблемы и решения

1. N+1 Problem (самая частая)

# ❌ Медленно - делает N+1 запрос
users = User.query.all()
for user in users:
    print(user.profile.bio)  # Дополнительный запрос для каждого пользователя!

# ✅ Быстро - один запрос с JOIN
users = User.query.options(joinedload(User.profile)).all()
for user in users:
    print(user.profile.bio)  # Данные уже загружены

2. Отсутствие индекса

-- Запрос медленный без индекса
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 123;

-- Добавляем индекс
CREATE INDEX idx_orders_customer_id ON orders(customer_id);

-- Теперь быстро
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 123;

3. Полнотекстовый поиск по большой колонке

-- ❌ Медленно
SELECT * FROM articles WHERE content LIKE '%pattern%';

-- ✅ Быстро - с полнотекстовым индексом
CREATE INDEX idx_articles_content ON articles USING GIN(to_tsvector('russian', content));

SELECT * FROM articles 
WHERE to_tsvector('russian', content) @@ plainto_tsquery('russian', 'pattern');

4. Денормализация для частых запросов

# Вместо подсчёта каждый раз
class Product:
    id: int
    name: str
    reviews_count: int  # Кэшируем подсчёт
    average_rating: float  # Кэшируем среднее

# Обновляем при добавлении нового отзыва
def add_review(product_id, rating):
    product = Product.query.get(product_id)
    product.reviews_count += 1
    product.average_rating = calculate_avg(product_id)
    db.commit()

Чеклист для поиска нагруженных запросов

  • Включить slow query log
  • Включить pg_stat_statements (или Performance Schema для MySQL)
  • Регулярно проверять EXPLAIN ANALYZE для часто используемых queries
  • Искать N+1 problems с помощью query count логирования
  • Проверить наличие необходимых индексов
  • Мониторить cache hit ratio
  • Использовать APM tools (DataDog, New Relic, Sentry) для профессионального мониторинга

Вывод: комбинируй встроенные DB инструменты + логирование в коде + external monitoring.

Как находить нагруженные SQL запросы в БД? | PrepBro