Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Встречал ли проблемы с ORM
Да, много. ORM - это мощный инструмент но у него есть недостатки.
Типичные проблемы с ORM
1. N+1 запросы
Классическая проблема:
# ПЛОХО
posts = Post.objects.all()
for post in posts: # 1000 постов
print(post.author.name) # Каждый раз новый запрос! 1001 запрос!
# ХОРОШО
posts = Post.objects.select_related('author')
for post in posts:
print(post.author.name) # 1 запрос
Как я это находил:
- Django Debug Toolbar
- Логирование SQL запросов
- Профилирование
2. Сложные JOIN'ы которые ORM не может выразить
# Нужна рекурсивная CTE для иерархии категорий
# ORM не может это выразить
# Пришлось использовать raw SQL:
with connection.cursor() as cursor:
cursor.execute('''
WITH RECURSIVE cat_tree AS (
SELECT id, name, parent_id FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id FROM categories c
INNER JOIN cat_tree ct ON c.parent_id = ct.id
)
SELECT * FROM cat_tree
''')
3. Performance проблемы с аннотирование
# ПЛОХО - медленно
users = User.objects.annotate(
post_count=Count('posts'),
comment_count=Count('comments'),
like_count=Count('likes'),
follower_count=Count('followers')
)
# Результат: Cartesian product! Дублирование рядов.
# ХОРОШО - отдельные запросы или кэш
post_counts = User.objects.values('id').annotate(
post_count=Count('posts')
)
4. Неправильный ORDER BY с GROUP BY
# ПЛОХО
posts = Post.objects.values('author').annotate(
count=Count('id')
).order_by('-created_at') # Ошибка! created_at не в GROUP BY
# ХОРОШО
posts = Post.objects.values('author').annotate(
count=Count('id'),
latest=Max('created_at')
).order_by('-latest')
5. Memory утечки с большими querysets
# ПЛОХО - загруженно всё в памяти
all_users = User.objects.all() # 1 млн пользователей в памяти!
# ХОРОШО - iterator() для streaming
for user in User.objects.all().iterator(chunk_size=1000):
process(user)
# Или используй bulk_create с batch_size
Серьёзные проблемы которые я встретил
Проблема 1: Race conditions с update
# Два процесса одновременно
Process 1: account.balance -= 100 # 1000 -> 900
Process 2: account.balance += 50 # 1000 -> 1050
# Результат: 1050 вместо 950! Потеря денег!
# Решение: SELECT FOR UPDATE
from django.db import transaction
with transaction.atomic():
account = Account.objects.select_for_update().get(id=1)
account.balance -= 100
account.save()
Проблема 2: Bulk операции не триггерят сигналы
# ПЛОХО
User.objects.filter(is_active=False).delete()
# post_delete сигнал НЕ вызывается!
# ХОРОШО
for user in User.objects.filter(is_active=False):
user.delete() # post_delete вызовется
# Или используй bulk_delete из пакетов
Проблема 3: Cascade delete может быть очень медленно
# ПЛОХО
# Пользователь с 10000 постов, каждый пост с 1000 комментов
user.delete() # 10 млн DELETE операций!
# Решение: удалить в БД напрямую без каскада или batches
Comment.objects.filter(post__author=user).delete()
Post.objects.filter(author=user).delete()
user.delete()
Когда я выбрал Raw SQL
# 1. Рекурсивные структуры (CTE)
# 2. Window функции (PARTITION BY, ROW_NUMBER)
# 3. Очень сложные аналитические запросы
# 4. Performance критичный код
# 5. Специфичные для БД функции
Как я дебажу ORM проблемы
from django.db import connection
from django.test.utils import override_settings
# 1. Смотрю сгенерированный SQL
print(User.objects.filter(name='John').query)
# 2. Считаю количество запросов
@override_settings(DEBUG=True)
def test():
users = User.objects.all()
for user in users:
user.posts.all() # N+1!
print(len(connection.queries))
# 3. Профилирую
import time
start = time.time()
users = User.objects.select_related('profile').all()
print(f"Time: {time.time() - start}")
# 4. Использую django-debug-toolbar
# Показывает все запросы в browser
Решения которые я применял
1. Кэширование результатов
from django.core.cache import cache
# Кэшируем expensive запрос
def get_user_stats(user_id):
cache_key = f'user_stats:{user_id}'
stats = cache.get(cache_key)
if stats is None:
stats = User.objects.filter(id=user_id).annotate(
post_count=Count('posts')
).first()
cache.set(cache_key, stats, 3600)
return stats
2. Денормализация
# Вместо аннотирование каждый раз
# Кэшируем в отдельный field
class User(models.Model):
post_count = models.IntegerField(default=0)
@classmethod
def update_stats(cls, user_id):
user = cls.objects.get(id=user_id)
user.post_count = user.posts.count()
user.save(update_fields=['post_count'])
3. Batch операции
# Вместо сотни UPDATE запросов
updates = []
for user in users:
user.last_login = now()
updates.append(user)
User.objects.bulk_update(updates, ['last_login'], batch_size=1000)
4. Raw SQL для critical path
# High performance критичный код
query = '''
SELECT u.id, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id
ORDER BY post_count DESC
LIMIT 100
'''
# Это быстрее чем ORM
Выводы
- ORM хороший для 90% случаев
- Остальные 10% требуют знания SQL
- N+1 - самая частая проблема
- Всегда профилируй перед оптимизацией
- Sometimes Raw SQL проще и быстрее
- Важно знать когда использовать каждый подход