2013-04-11 2 views
7

У меня есть простая модель с 3 полями 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') 

Для просмотра списка в администраторе django я получаю 4 запроса. Один из них - SELECT с 3 INNER JOINS. Этот запрос - способ замедления. Замена INNER JOINs на STRAIGHT_JOIN устранит проблему. Есть ли способ исправить созданный администратором запрос непосредственно перед его оценкой?

ответ

4

Я реализовал исправление INNER JOIN для Django ORM, он будет использовать STRAIGHT_JOIN в случае заказа с INNER JOIN и. Я поговорил с разработчиками ядра Django, и мы решили сделать это как отдельный бэкэнд на данный момент. Таким образом, вы можете проверить это здесь: https://pypi.python.org/pypi/django-mysql-fix

Однако есть еще одно обходное решение. Используйте фрагмент из ответа Джеймса, но заменить select_related с:

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

Это отменит INNER JOIN и использовать 4 отдельных запросов: 1 для выборки автомобилей и 3 других с car_id IN (...).

ОБНОВЛЕНИЕ: Я нашел еще одно обходное решение. Когда вы укажете null = True в поле ForeignKey, Django будет использовать LEFT OUTER JOINs вместо INNER JOIN. LEFT OUTER JOIN работает без проблем с производительностью в этом случае, но вы можете столкнуться с другими проблемами, о которых я еще не знаю.

+1

'select_related ('')' не работал для меня, это не мешало внутреннему соединению. Но, указав 'list_select_related = []' в классе admin, он сделал трюк! – rednaw

2

Вы можете перезаписать

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

метод в классе администратора, унаследованной от ModelAdmin класса

что-то подобное (но этот вопрос довольно старый): Django Admin: Getting a QuerySet filtered according to GET string, exactly as seen in the change list?

+0

У меня есть доступ к ChangeList.get_query_set, но если я залатать его с Car.objects. raw sql query, который paginator жалуется на «RawQuerySet» не имеет len() »... Мне почему-то нужен запрос на более позднем этапе для исправления sql – Titusz

+0

Я думаю, что вы можете попробовать подкласс RawQuerySet. – singer

+0

Кстати, вы проверили свои индексы базы данных? Три объединения не слишком много. – singer

1

Хорошо, я нашел способ исправления созданного admin запроса. Это некрасиво, но , кажется, работает:

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

Решение не работает, как вы могли ожидать. .raw() не выполняет сопоставление с объединенными моделями. В этом случае он запрашивает данные с помощью STRAIGHT_JOIN и отбрасывает все поля, которые не существуют в модели, и после этого он будет обрабатывать отдельные запросы для связанных полей. Таким образом, в этом переопределении нет никакой пользы. –

1

я наткнулся на тот же вопрос в админке Django (версия 1.4.9), где довольно простые администраторы листинга страница была очень медленно при поддержке MySQL.

В моем случае это было вызвано ChangeList.get_query_set() методом добавления чрезмерно широкого глобального select_related() в запросе набора, если любые поля в list_display было много-к-одному отношения. Для правильной базы данных (cough PostgreSQL cough) это не будет проблемой, но это было для MySQL еще раз, чем несколько подключений были вызваны таким образом.

Чистое решение, которое я нашел, состояло в том, чтобы заменить глобальную директиву select_related() более целенаправленной, которая только соединяла таблицы, которые были действительно необходимы. Это было достаточно легко сделать, позвонив select_related() с явными именами отношений.

Этот подход, вероятно, заканчивается обмен соединениями в базе данных для нескольких последующих запросов, но если MySQL задыхается от большого запроса, многие мелкие могут быть быстрее для вас.

Вот что я сделал, более или менее:

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

У меня были медленные запросы администратора MySQL, и найти самое легкое решение было добавить STRAIGHT_JOIN в запрос. Я понял способ добавить это к QuerySet, а не быть вынужденным перейти на .raw(), который не будет работать с администратором и будет открыт с его помощью как часть django-mysql. Вы можете просто:

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

Вы можете просто указать list_select_related =() для предотвращения Джанго с помощью внутреннего соединения:

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