2013-04-11 14 views
7

Ho un modello semplice con 3 campi ForeignKey.Django admin MySQL slow INNER JOIN

class Car(models.Model): 
    wheel = models.ForeignKey('Wheel', related_name='wheels') 
    created = models.DateTimeField(auto_now_add=True) 
    max_speed = models.PositiveSmallIntegerField(null=True) 
    dealer = models.ForeignKey('Dealer') 
    category = models.ForeignKey('Category') 

Per la visualizzazione elenco nell'amministratore di django ottengo 4 query. Uno di questi è un SELECT con 3 INNER JOINS. Quella query è un modo per rallentare. Sostituire i JOIN INNER con STRAIGHT_JOIN risolverebbe il problema. C'è un modo per applicare la patch alla query generata dall'amministratore prima che venga valutata?

risposta

4

Ho implementato una correzione per INNER JOIN per Django ORM, userà STRAIGHT_JOIN in caso di ordinare con INNER JOIN. Ho parlato con i core-dev di Django e per ora abbiamo deciso di farlo come back-end separato. Quindi puoi verificarlo qui: https://pypi.python.org/pypi/django-mysql-fix

Tuttavia, c'è un'altra soluzione alternativa. Utilizzare un frammento dalla risposta di James, ma sostituite con select_related:

qs = qs.select_related('').prefetch_related('wheel', 'dealer', 'category') 

Si annullerà INNER JOIN e utilizzare 4 query separate: 1 per andare a prendere auto e altri 3 con car_id IN (...).

UPDATE: Ho trovato un'altra soluzione alternativa. Una volta specificato null = True nel campo ForeignKey, Django utilizzerà LEFT OUTER JOINs invece di INNER JOIN. LEFT OUTER JOIN funziona senza problemi di prestazioni in questo caso, ma potresti dover affrontare altri problemi di cui non sono ancora a conoscenza.

+1

Il 'select_related ('')' non ha funzionato per me, non ha impedito il verificarsi del join interno. Ma specificando 'list_select_related = []' nella classe admin ha fatto il trucco! – rednaw

2

Si potrebbe sovrascrivere il metodo

def changelist_view(self, request, extra_context=None): 

nella tua classe amministratore ereditata dalla ModelAdmin classe

qualcosa di simile (ma la questione è piuttosto vecchio): Django Admin: Getting a QuerySet filtered according to GET string, exactly as seen in the change list?

+0

ho accesso al ChangeList.get_query_set ma se Patch con un Car.objects. sql raw query the paginator si lamenta con "'RawQuerySet' non ha len()" ... In qualche modo ho bisogno del queryset in una fase successiva per correggere sql – Titusz

+0

Penso che potresti provare a sottoclasse RawQuerySet. – singer

+0

BTW, hai controllato gli indici del tuo database? Tre join non sono troppi. – singer

1

Ok, ho trovato un modo per applicare la patch alla Query generata dall'amministratore. E 'brutto, ma sembra funzionare:

class CarChangeList(ChangeList): 

    def get_results(self, request): 
     """Override to patch ORM generated SQL""" 
     super(CarChangeList, self).get_results(request) 
     original_qs = self.result_list 
     sql = str(original_qs.query) 
     new_qs = Car.objects.raw(sql.replace('INNER JOIN', 'STRAIGHT_JOIN')) 

     def patch_len(self): 
      return original_qs.count() 
     new_qs.__class__.__len__ = patch_len 

     self.result_list = new_qs 


class CarAdmin(admin.ModelAdmin): 

    list_display = ('wheel', 'max_speed', 'dealer', 'category', 'created') 

    def get_changelist(self, request, **kwargs): 
     """Return custom Changelist""" 
     return CarChangeList 

admin.site.register(Rank, RankAdmin) 
+0

La soluzione non funziona come dovrebbe. .raw() non esegue il mapping ai modelli uniti. In questo caso richiederà dati con STRAIGHT_JOIN e lascerà cadere tutti i campi che non esistono nel modello, dopodiché farà richieste separate per i campi correlati. Quindi non c'è alcun vantaggio in questa sostituzione. –

1

mi sono imbattuto lo stesso problema in admin Django (versione 1.4.9), dove abbastanza semplici pagine di messa in vendita di amministrazione erano molto lento quando sostenuta da MySQL.

Nel mio caso è stato causato dal metodo ChangeList.get_query_set() l'aggiunta di un eccessivamente ampia globale select_related() alla query impostare se eventuali campi in list_display erano molti-a-uno. Per un database corretto (tosse PostgreSQL tosse) questo non sarebbe un problema, ma è stato per MySQL ancora una volta che alcuni join sono stati attivati ​​in questo modo.

La soluzione più pulita che ho trovato è stata quella di sostituire la direttiva globale select_related() con una più mirata che ha unito solo le tabelle realmente necessarie. Questo è stato abbastanza facile da fare chiamando select_related() con nomi di relazioni espliciti.

Questo approccio probabilmente finisce per scambiare i join nel database per più query di follow-up, ma se MySQL sta soffocando sulla query di grandi dimensioni, molte di esse potrebbero essere più veloci per te.

Ecco quello che ho fatto, più o meno:

from django.contrib.admin.views.main import ChangeList 


class CarChangeList(ChangeList): 

    def get_query_set(self, request): 
     """ 
     Replace a global select_related() directive added by Django in 
     ChangeList.get_query_set() with a more limited one. 
     """ 
     qs = super(CarChangeList, self).get_query_set(request) 
     qs = qs.select_related('wheel') # Don't join on dealer or category 
     return qs 


class CarAdmin(admin.ModelAdmin): 

     def get_changelist(self, request, **kwargs): 
      return CarChangeList 
1

Ho avuto query di amministrazione lente su MySQL e ho trovato che la soluzione più semplice era aggiungere STRAIGHT_JOIN alla query. Ho trovato un modo per aggiungere questo a invece di essere forzato per andare a .raw(), che non funzionerà con l'amministratore, e l'ho aperto come parte di django-mysql. Si può quindi solo:

def get_queryset(self, request): 
    qs = super(MyAdmin, self).get_queryset(request) 
    return qs.straight_join() 
3

Si può solo specificare list_select_related =() per evitare di utilizzare Django join interno:

class CarAdmin(admin.ModelAdmin): 
    list_select_related =()