From 417e7c34bc899a58bd42ed2a4ee8d391aa9eba34 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 9 Mar 2026 18:26:32 +0800 Subject: [PATCH] Fix submission list API performance: N+1 queries, missing indexes, and in-memory pagination - Add select_related("user") in update_score() to eliminate N+1 on rating.user.role - Return QuerySet directly from list_submissions so @paginate works at DB level - Defer html/css/js fields in list queries to reduce data transfer - Add db_index on flag field for filtered queries - Add select_related("task", "user") to get_submission endpoint - Replace manual list() serialization with resolve_* methods on SubmissionOut Co-Authored-By: Claude Opus 4.6 --- submission/api.py | 18 +++---- submission/migrations/0005_add_flag_index.py | 18 +++++++ submission/models.py | 9 ++-- submission/schemas.py | 52 +++++++++++++------- 4 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 submission/migrations/0005_add_flag_index.py diff --git a/submission/api.py b/submission/api.py index 517c22a..997bbbb 100644 --- a/submission/api.py +++ b/submission/api.py @@ -56,7 +56,9 @@ def list_submissions(request, filters: SubmissionFilter = Query(...)): """ 获取提交列表,支持按任务和用户过滤 """ - submissions = Submission.objects.select_related("task", "user") + submissions = Submission.objects.select_related("task", "user").defer( + "html", "css", "js" + ) if filters.task_id: task = get_object_or_404(Task, id=filters.task_id) @@ -76,12 +78,7 @@ def list_submissions(request, filters: SubmissionFilter = Query(...)): ) submissions = submissions.annotate(my_score=user_rating_subquery) - def get_submission_data(submission): - """从 submission 对象构建 SubmissionOut 数据""" - my_score = getattr(submission, "my_score", None) or 0 - return SubmissionOut.list(submission, {submission.id: my_score}) - - return [get_submission_data(submission) for submission in submissions] + return submissions @router.get("/{submission_id}", response=SubmissionOut) @@ -90,10 +87,11 @@ def get_submission(request, submission_id: UUID): """ 获取单个提交的详细信息 """ - submission = get_object_or_404(Submission, id=submission_id) + submission = get_object_or_404( + Submission.objects.select_related("task", "user"), id=submission_id + ) rating = ( - Rating.objects.select_related("user", "submission") - .filter(user=request.user, submission=submission) + Rating.objects.filter(user=request.user, submission=submission) .first() ) return SubmissionOut.get(submission, rating) diff --git a/submission/migrations/0005_add_flag_index.py b/submission/migrations/0005_add_flag_index.py new file mode 100644 index 0000000..5a66622 --- /dev/null +++ b/submission/migrations/0005_add_flag_index.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.1 on 2026-03-09 10:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submission', '0004_submission_flag'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='flag', + field=models.CharField(blank=True, choices=[('red', '值得展示'), ('blue', '需要讲解'), ('green', '优秀作品'), ('yellow', '需要改进')], db_index=True, default=None, max_length=10, null=True, verbose_name='标记'), + ), + ] diff --git a/submission/models.py b/submission/models.py index 6165cb8..f396b51 100644 --- a/submission/models.py +++ b/submission/models.py @@ -44,6 +44,7 @@ class Submission(TimeStampedModel): null=True, blank=True, default=None, + db_index=True, verbose_name="标记", ) @@ -63,7 +64,7 @@ class Submission(TimeStampedModel): """ 更新当前Submission的分数 """ - ratings = self.ratings.all() + ratings = list(self.ratings.select_related("user").all()) super_score = 0.0 admin_score = 0.0 @@ -77,13 +78,13 @@ class Submission(TimeStampedModel): else: normal_score += rating.score - if ratings.exists(): + if ratings: total_score = super_score * 0.5 + admin_score * 0.3 + normal_score * 0.2 - self.score = total_score / ratings.count() + self.score = total_score / len(ratings) else: self.score = 0.0 - self.save() + self.save(update_fields=["score"]) def save(self, *args, **kwargs): super().save(*args, **kwargs) diff --git a/submission/schemas.py b/submission/schemas.py index 6f999ce..5425c63 100644 --- a/submission/schemas.py +++ b/submission/schemas.py @@ -20,7 +20,7 @@ class SubmissionOut(Schema): task_title: str task_type: Literal["tutorial", "challenge"] score: float - my_score: int + my_score: int = 0 html: Optional[str] = None css: Optional[str] = None js: Optional[str] = None @@ -30,22 +30,40 @@ class SubmissionOut(Schema): modified: str @staticmethod - def list(submission, rating_dict): - return { - "id": submission.id, - "userid": submission.user.id, - "username": submission.user.username, - "task_id": submission.task.id, - "task_display": submission.task.display, - "task_title": submission.task.title, - "task_type": submission.task.task_type, - "score": submission.score, - "my_score": rating_dict.get(submission.id, 0), - "conversation_id": submission.conversation_id, - "flag": submission.flag, - "created": submission.created.isoformat(), - "modified": submission.modified.isoformat(), - } + def resolve_userid(obj): + return obj.user.id + + @staticmethod + def resolve_username(obj): + return obj.user.username + + @staticmethod + def resolve_task_id(obj): + return obj.task.id + + @staticmethod + def resolve_task_display(obj): + return obj.task.display + + @staticmethod + def resolve_task_title(obj): + return obj.task.title + + @staticmethod + def resolve_task_type(obj): + return obj.task.task_type + + @staticmethod + def resolve_my_score(obj): + return getattr(obj, "my_score", None) or 0 + + @staticmethod + def resolve_created(obj): + return obj.created.isoformat() + + @staticmethod + def resolve_modified(obj): + return obj.modified.isoformat() @staticmethod def get(submission, rating):