Выполнение необработанного SQL перед каждым select для конкретной модели
У меня есть сложный отчет в Django, который пишется как сырой SQL, а затем сохраняется в базе данных как представление базы данных.
У меня есть модель Django (managed=False), привязанная к представлению базы данных, так что я могу использовать django ORM и django rest framework поверх представления базы данных. Эту практику я использовал в нескольких проектах, и она отлично работает.
Для одного конкретного отчета я обнаружил, что мне нужно вмешаться в планировщик запросов postgres, чтобы заставить его работать быстрее. Перед каждым оператором SELECT для этой модели (представления базы данных), чтобы SQL выполнялся быстрее, мне нужно сделать:
SET enable_nestloop TO off;
Запрос довольно сложный, с агрегатными функциями и объединениями подзапросов, поэтому я отказался от попыток его оптимизировать. Отключение nestloop решает все мои проблемы с производительностью, и меня это устраивает, если только я включу его обратно после выполнения запроса. Вот объяснение проблемы и ее решение. ( Каковы подводные камни при установке enable_nestloop в OFF)
На данный момент я оборачиваю запрос Report в транзакцию базы данных и устанавливаю enable_nestloop
только в этой транзакции, с SET LOCAL
.
with transaction.atomic(), connection.cursor() as cursor:
cursor.execute("SET LOCAL enable_nestloop = off;")
queryset = self.__build_queryset(session, request.query_params)
serializer = MySerializer(queryset, many=True)
response_data = serializer.data
return Response(response_data)
Это работает хорошо, но мне нужно не забывать выполнять эту логику каждый раз перед выполнением оператора SELECT для этой конкретной модели, иначе это будет мучительно медленно.
Я ищу метод, который позволит мне выполнять логику параметра SET перед каждым запросом SELECT в этой модели, а затем устанавливать параметр обратно в исходное значение. Можно ли это сделать на уровне модели или менеджера?
Типа того.
Вероятно, вы не захотите вставлять его в менеджер get_queryset()
напрямую, потому что, как вы, вероятно, знаете, кверисеты ленивы, и это приведет к отключению nestloop за некоторое произвольное время до того, как вы действительно обратитесь к базе данных с курсором. Это увеличивает вероятность того, что любые параллельные или одновременные запросы будут страдать от последствий отключения nestloop, несмотря на то, что вы фактически не обращались к запросу, который выиграл бы от отключения nestloop.
Вы можете переопределить некоторые методы менеджера, но это будет утомительно, и вы не сможете эффективно использовать .filter()
(так как он возвращает ленивый кверисет, что приводит к той же проблеме, о которой говорилось выше). Если вам нужно получать только отдельные элементы, то перегрузки .get()
менеджера, вероятно, будет достаточно.
Более общий (и, IMO, более поддерживаемый, более django-esque) подход заключается в переопределении самого кверисета и откладывании обновления настроек до тех пор, пока вы действительно не обратитесь к базе данных. Это также дает дополнительный бонус в том, что вы можете использовать это же решение для любой модели, над которой вам нужно поработать, просто изменив ее менеджер, как показано ниже.
class NestloopQuerySet(QuerySet):
_nestloop_sql_enable_string = "SET enable_nestloop TO on;"
_nestloop_sql_disable_string = "SET enable_nestloop TO off;"
def _fetch_all(self):
with connection.cursor() as cursor:
try:
cursor.execute(self._nestloop_sql_disable_string)
super()._fetch_all()
finally:
cursor.execute(self._nestloop_sql_enable_string)
class NestloopModelManager(models.Manager):
def get_queryset(self):
return NestloopQuerySet(self.model, using=self._db)
QuerySet._fetch_all()
лежит в основе всех способов поиска информации в базе данных, поэтому перегрузки этого метода должно быть достаточно для всех возможных случаев использования, включая итерацию.