我在 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 参数逻辑,然后将参数设置回原始值。我可以在模特或经理级别上执行此操作吗?
有点。
您可能不想直接将其插入管理器的
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()
是所有数据库检索途径的核心,因此重载此方法应该足以满足所有可能的用例,包括迭代。