#34639: MySQL 8.0 hangs indefinitely when using the admin search with a Foreign Key and Annotate --------------------------------+-------------------------------------- Reporter: Nicolas Lupien | Owner: nobody Type: Bug | Status: new Component: contrib.admin | Version: 4.2 Severity: Normal | Resolution: Keywords: mysql | Triage Stage: Unreviewed Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 --------------------------------+-------------------------------------- Description changed by Nicolas Lupien:
Old description: > We've moved to MySQL 8.0 in order to use Django 4.2 but our production > system went down and we reverted to using MySQL 5.7 with Django 4.1. > We've currently found a workaround that I'll add at the end of the bug > report. > > If we use the search function of the admin on model with a foreign key > and we override ModelAdmin.get_queryset with annotate, the search freezes > our database. It had the same effect on Google Cloud SQL and on a local > docker image of MySQL 8.0 and it works fine on both environment when > using MySQL 5.7. > > The code: > > models.py > > {{{ > class Organization(models.Model): > name = models.CharField(max_length=255) > > class Member(models.Model): > name = models.CharField(max_length=255) > organization = models.ForeignKey(Organization, > on_delete=models.CASCADE, null=True) > }}} > > admin.py > > {{{ > class OrganizationAdmin(admin.ModelAdmin): > search_fields = ["name", "member__name"] > list_display = ["name", "member_count"] > > class Meta: > model = models.Organization > > def get_queryset(self, request): > return super().get_queryset(request).annotate(Count("member")) > > def member_count(self, instance): > return instance.member__count > }}} > > I found that the ChangeList applies the override to get_queryset > containing the annotate multiple times making the query extremely > expensive. Give only 500 members it goes through 125,000,000 (500 * 500 * > 500) rows. > > The workaround: If we override the ChangeList queryset, the call to > annotate happens only once and the query is fine. > > {{{ > class CustomChangeList(ChangeList): > def get_queryset(self, request): > return > super().get_queryset(request).annotate(Count("locker_connectors")) > > class OrganizationAdmin(admin.ModelAdmin): > search_fields = ["name", "member__name"] > list_display = ["name", "member_count"] > > class Meta: > model = models.Organization > > def member_count(self, instance): > return instance.member__count > > def get_changelist(self, request, **kwargs): > return CustomChangeList > }}} > > I created a repo with more details and the complete steps to reproduce > the issue: https://github.com/betaflag/django-sqlbugdemo New description: We've moved to MySQL 8.0 in order to use Django 4.2 but our production system went down and we reverted to using MySQL 5.7 with Django 4.1. We've currently found a workaround that I'll add at the end of the bug report. If we use the search function of the admin on model with a foreign key and we override ModelAdmin.get_queryset with annotate, the search freezes our database. It had the same effect on Google Cloud SQL and on a local docker image of MySQL 8.0 and it works fine on both environment when using MySQL 5.7. The code: models.py {{{ class Organization(models.Model): name = models.CharField(max_length=255) class Member(models.Model): name = models.CharField(max_length=255) organization = models.ForeignKey(Organization, on_delete=models.CASCADE, null=True) }}} admin.py {{{ class OrganizationAdmin(admin.ModelAdmin): search_fields = ["name", "member__name"] list_display = ["name", "member_count"] class Meta: model = models.Organization def get_queryset(self, request): return super().get_queryset(request).annotate(Count("member")) def member_count(self, instance): return instance.member__count }}} I found that the ChangeList applies the override to get_queryset containing the annotate multiple times making the query extremely expensive. Give only 500 members it goes through 125,000,000 (500 * 500 * 500) rows. The workaround: If we override the ChangeList queryset, the call to annotate happens only once and the query is fine. {{{ class CustomChangeList(ChangeList): def get_queryset(self, request): return super().get_queryset(request).annotate(Count("member")) class OrganizationAdmin(admin.ModelAdmin): search_fields = ["name", "member__name"] list_display = ["name", "member_count"] class Meta: model = models.Organization def member_count(self, instance): return instance.member__count def get_changelist(self, request, **kwargs): return CustomChangeList }}} I created a repo with more details and the complete steps to reproduce the issue: https://github.com/betaflag/django-sqlbugdemo -- -- Ticket URL: <https://code.djangoproject.com/ticket/34639#comment:3> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/0107018897450523-c6b9a108-46ac-45a2-8c83-30c8c796b2e7-000000%40eu-central-1.amazonses.com.