在每次选择特定模型之前执行原始 SQL

问题描述 投票:0回答:1

我在 Django 中有一个复杂的报告,它是用原始 SQL 编写的,然后作为数据库视图存储在数据库中。

我有一个 Django 模型(托管 = False),与该数据库视图绑定,以便我可以在数据库视图之上利用 django ORM 和 django Rest 框架。这种做法我已经在几个项目中使用过,并且效果很好。

对于一份特定的报告,我发现我需要干预 postgres 查询规划器,以使其运行得更快。在该模型(数据库视图)上的每个 SELECT 语句之前,为了让 SQL 运行得更快,我需要执行以下操作:

SET enable_nestloop TO off;

查询相当复杂,有聚合函数,还有子查询连接,所以我放弃了优化它的尝试。关闭 Nestloop 可以解决我所有的性能问题,只要我在查询后重新打开它,我就可以接受。这是问题和解决方案的解释。 (将enable_nestloop设置为OFF有哪些陷阱

目前,我所做的是将报告查询包装在数据库事务中,并仅在该事务中使用

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 语句之前都执行这个逻辑,否则速度会非常慢。

我正在寻找一种方法,允许我在此模型上的每个 SELECT 查询之前执行 SET 参数逻辑,然后将参数设置回原始值。我可以在模特或经理级别上执行此操作吗?

django django-models django-orm
1个回答
0
投票

有点。

您可能不想直接将其插入管理器的

get_queryset()
中,因为您可能知道查询集是惰性的,这最终会在您实际使用光标访问数据库之前在任意时间内禁用nestloop 。这增加了任何并发或同步查询遭受 Nestloop 关闭后果的可能性,尽管您实际上并未访问将受益于 Nestloop 关闭的查询。

您可以覆盖某些管理器方法,但这使用起来会很乏味,并且您将无法有效地使用

.filter()
(因为它返回一个惰性查询集,导致与上面讨论的相同的问题)。

一种更通用(在我看来,更易于维护、更像 django 风格)的方法是覆盖查询集本身并推迟设置更新,直到实际访问数据库为止。这还有一个额外的好处,那就是能够对您需要处理的任何给定模型使用相同的解决方案,只需修改其管理器,与下面类似。

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()
是所有数据库检索途径的核心,因此重载此方法应该足以满足所有可能的用例,包括迭代。

© www.soinside.com 2019 - 2024. All rights reserved.