106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
#!/usr/bin/env python
|
||
"""
|
||
Скрипт для демонстрации N+1 проблемы в текущей реализации
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import django
|
||
|
||
# Setup Django
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eusrr_backend.settings")
|
||
django.setup()
|
||
|
||
from django.conf import settings
|
||
from django.contrib.auth import get_user_model
|
||
from django.db import connection, reset_queries
|
||
from django.db.models import Count, OuterRef, Q, Subquery
|
||
from django.db.models.functions import Coalesce
|
||
|
||
from communications.models import Chat, ChatReadState, Message
|
||
|
||
# Включаем отслеживание SQL
|
||
settings.DEBUG = True
|
||
|
||
Employee = get_user_model()
|
||
|
||
|
||
def test_current_implementation():
|
||
"""Текущая реализация с подзапросами"""
|
||
|
||
user = Employee.objects.first()
|
||
if not user:
|
||
print("Нет пользователей в базе")
|
||
return
|
||
|
||
print(f"Тестируем для пользователя: {user}")
|
||
print("=" * 80)
|
||
|
||
# Сбрасываем счетчик запросов
|
||
reset_queries()
|
||
|
||
# Текущая реализация из ChatViewSet.get_queryset()
|
||
read_state_subq = ChatReadState.objects.filter(
|
||
chat=OuterRef("pk"), user=user
|
||
).values("last_read_message_id")[:1]
|
||
|
||
unread_count_subq = (
|
||
Message.objects.filter(
|
||
chat=OuterRef("pk"),
|
||
id__gt=Coalesce(Subquery(read_state_subq), 0),
|
||
is_deleted=False,
|
||
)
|
||
.exclude(author=user)
|
||
.values("chat")
|
||
.annotate(count=Count("id"))
|
||
.values("count")
|
||
)
|
||
|
||
chats = (
|
||
Chat.objects.filter(
|
||
Q(participants=user)
|
||
| Q(
|
||
department__in=user.departments_links.filter(is_active=True).values(
|
||
"department"
|
||
)
|
||
)
|
||
| Q(include_all_employees=True)
|
||
)
|
||
.annotate(unread_count=unread_count_subq)
|
||
.distinct()[:10]
|
||
) # Берем первые 10 чатов
|
||
|
||
# Принудительно выполняем запрос
|
||
chat_list = list(chats)
|
||
|
||
print(f"\nЗагружено чатов: {len(chat_list)}")
|
||
print(f"Количество SQL запросов: {len(connection.queries)}")
|
||
print("\n" + "=" * 80)
|
||
|
||
# Показываем первые 3 запроса
|
||
for i, query in enumerate(connection.queries[:3], 1):
|
||
print(f"\nЗапрос #{i}:")
|
||
print(query["sql"][:500])
|
||
if len(query["sql"]) > 500:
|
||
print("... (обрезано)")
|
||
print(f"Время: {query['time']}s")
|
||
|
||
if len(connection.queries) > 3:
|
||
print(f"\n... еще {len(connection.queries) - 3} запросов")
|
||
|
||
print("\n" + "=" * 80)
|
||
print("\nПРОБЛЕМА:")
|
||
print("Несмотря на то, что это ОДИН SQL запрос, внутри него есть")
|
||
print("ПОДЗАПРОСЫ (subqueries) для каждого чата в SELECT clause.")
|
||
print("PostgreSQL выполняет их для КАЖДОЙ строки результата.")
|
||
print("\nЭто не классический N+1, но похожая проблема производительности.")
|
||
|
||
return chat_list
|
||
|
||
|
||
if __name__ == "__main__":
|
||
test_current_implementation()
|