← Назад к вопросам
Как находить нагруженные 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.