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

Какие шаги предпримешь, если SQL-запрос долго выполняется?

1.3 Junior🔥 181 комментариев
#Базы данных (SQL)

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

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

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

Какие шаги предпримешь, если SQL-запрос долго выполняется?

Медленный SQL запрос — одна из самых частых причин деградации производительности. Существует систематический подход к диагностике и оптимизации.

1. Измерить время выполнения

import time
import logging

logger = logging.getLogger(__name__)

def measure_query_time(query: str, params: dict = None):
    start = time.time()
    try:
        result = db.execute(query, params)
        duration = time.time() - start
        logger.info("query_executed", query=query[:100], duration_ms=duration * 1000)
        return result
    except Exception as e:
        duration = time.time() - start
        logger.exception("query_failed", query=query[:100], duration_ms=duration * 1000)
        raise

2. Использовать EXPLAIN для анализа плана

EXPLAIN ANALYZE SELECT * FROM orders 
WHERE user_id = 123 AND created_at > NOW() - INTERVAL '30 days';

-- Seq Scan on orders (cost=0.00..12345.00) — ❌ full scan
-- Нужен индекс!

3. Добавить индексы

CREATE INDEX idx_orders_user_id_created_at 
ON orders(user_id, created_at);

4. Проверить на N+1 query problem

# ❌ Плохо — N+1 queries
users = db.query(User).all()
for user in users:
    print(user.orders)  # N queries!

# ✅ Хорошо — eager loading
users = db.query(User).options(joinedload(User.orders)).all()

5. Проверить фильтры в WHERE

-- ❌ Плохо — функция в WHERE
WHERE LOWER(email) = 'test@example.com';
WHERE price * 1.2 > 100;

-- ✅ Хорошо — по самому столбцу
WHERE email = 'test@example.com';
WHERE price > 100 / 1.2;

6. Оптимизировать SELECT

-- ❌ Плохо
SELECT * FROM users;

-- ✅ Хорошо
SELECT id, email, created_at FROM users;

7. Добавить LIMIT для больших результатов

SELECT * FROM logs ORDER BY created_at DESC LIMIT 100 OFFSET 0;

8. Проверить статистику таблицы

ANALYZE table_name;
VACUUM ANALYZE table_name;

9. Использовать батчинг вместо цикла

# ❌ Плохо
for item in items:
    db.execute(f"INSERT INTO products VALUES ({item.id})")
    db.commit()

# ✅ Хорошо
db.bulk_insert_mappings(Product, items)
db.commit()

10. Использовать параметризованные запросы

# ❌ Плохо — SQL injection
query = f"SELECT * FROM users WHERE age > {age}"

# ✅ Хорошо
from sqlalchemy import text
query = text("SELECT * FROM users WHERE age > :age")
db.execute(query, {'age': age})

11. Переписать сложный запрос

-- ❌ Плохо — подзапрос в IN
SELECT u.* FROM users u
WHERE u.id IN (SELECT user_id FROM orders WHERE total > 1000);

-- ✅ Хорошо — JOIN
SELECT DISTINCT u.* FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.total > 1000;

12. Кеширование

import redis
import json

redis_client = redis.Redis()

def get_user_orders_cached(user_id: int):
    cache_key = f"user_orders:{user_id}"
    cached = redis_client.get(cache_key)
    
    if cached:
        return json.loads(cached)
    
    result = db.query(Order).filter(Order.user_id == user_id).all()
    redis_client.setex(cache_key, 300, json.dumps([o.to_dict() for o in result]))
    return result

13. Полный чеклист оптимизации

  1. Измеряем время (EXPLAIN ANALYZE)
  2. Проверяем на N+1 query problem
  3. Добавляем недостающие индексы
  4. Избегаем функций в WHERE
  5. Выбираем только нужные столбцы
  6. Используем пагинацию (LIMIT/OFFSET)
  7. Используем батчинг для bulk операций
  8. Добавляем кеширование (Redis)
  9. Переписываем запрос на более эффективный
  10. Масштабируем БД (реплики, партиционирование)

Итог: Оптимизация требует систематического подхода: измерить → проанализировать → добавить индексы → оптимизировать → кешировать. Не угадывай — профилируй!

Какие шаги предпримешь, если SQL-запрос долго выполняется? | PrepBro