← Назад к вопросам
Какие шаги предпримешь, если 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. Полный чеклист оптимизации
- Измеряем время (EXPLAIN ANALYZE)
- Проверяем на N+1 query problem
- Добавляем недостающие индексы
- Избегаем функций в WHERE
- Выбираем только нужные столбцы
- Используем пагинацию (LIMIT/OFFSET)
- Используем батчинг для bulk операций
- Добавляем кеширование (Redis)
- Переписываем запрос на более эффективный
- Масштабируем БД (реплики, партиционирование)
Итог: Оптимизация требует систематического подхода: измерить → проанализировать → добавить индексы → оптимизировать → кешировать. Не угадывай — профилируй!