【问题标题】:SubQuery in Django 1.11.8 is very slow - can I speed it up?Django 1.11.8 中的子查询非常慢 - 我可以加快速度吗?
【发布时间】:2018-01-08 15:28:39
【问题描述】:

我有一些表在两个单独的 Django 模型中没有通过 FK 关系连接,我正在尝试做一个SubQuery。数据如下所示:

# "master" data table - reflects real property ownership by humans
# there are often changes to property ownership
class OwnershipRecord(Model):
  parcel = CharField(max_length=10, unique=True)
  owner_name = ...
  other data fields ...

# Poor man's 'elastic search' or an index of sorts for OwnershipRecord
class Lead(Model):
  ownership_record = OneToOneField(OwnershipRecord)
  preforeclosure = BooleanField(default=False)
  aggregated data/booleans/etc...

# "descriptor" table for a property
# there are not often changes to a property's physical traits
ResidentialMasterRecord(Model):
  parcel = CharField(max_length=10, unique=True)  # These are the SAME as OwnershipRecord
  livablesqft = ...
  lotsqft = ...

所以我正在研究一个可以按平方英尺过滤Lead 对象的查询。 LeadOwnershipRecord 相关,但与 ResidentialMasterRecord 没有关系 - 它以“事实表”的形式存在,类似于特定地址的一组坐标。

我认为SubQuery 可以在这种情况下工作,我可以从OwnershipRecordResidentialMasterRecord 中引用parcel 来实时链接两者。

这是非常慢。这是我正在尝试的查询:

from django.db.models import OuterRef, SubQuery
from myapp.models import OwnershipRecord, Lead, ResidentialMasterRecord

RMR_SQ = ResidentialMasterRecord.objects \
           .filter(parcel=OuterRef("ownership_record__parcel"))
qs = Lead.objects.select_related('ownership_record') \
         .annotate(sqft=SubQuery(RMR_SQ.values("livablesqft")[:1])) \
         .filter(sqft__gte=1500)

我正在查看 15-45 分钟范围内的查询时间 - 但我最终会得到结果... 关于如何在保持非外键链接结构的同时加快这件事的任何想法?


Django 1.11.8 PostgreSQL 9.5 Droplet 带 8GB RAM,4 核

【问题讨论】:

  • 我们在这里讨论了多少条记录?最好用于每个表/模型。您还可以包括每个模型中的所有字段吗?它可能有助于画出更好的画面(无论如何对我来说)。
  • 外键列是否被索引?如果您有权访问 Postgresql 日志,您能否提取正在执行的完整查询,使用 EXPLAIN (ANALYZE, BUFFERS, VERBOSE) your_query 运行它并将其粘贴到 explain.depesz.com 以轻松查看减速可能来自何处。
  • FK 列应始终被索引,否则您将获得全表扫描而不是索引扫描。在小表上这很好,但在你的情况下,如果它们还没有被索引,你会看到一些好处。注意,FK 列默认不会在子端索引,但父端会有索引(由主键或唯一约束自动创建)。
  • @PANDAStack 没关系。我重新阅读它,我现在看到了重要的部分。我想保持“绝对数据量”选项处于打开状态,但这显然不是正在发生的事情。 bma 关于索引 FK 列的建议很可能是正确的路径。
  • 另外,您也可以在其他列上添加索引。通常,当有人询问慢速数据库查询时,我的第一个问题是“您是否在表上构建了任何索引?”答案通常是否定的,或者我不知道。

标签: django postgresql


【解决方案1】:

这个答案是 @bma 和 @wholevinski 的 cmets 灵感的结果。

Django docs中所述,

在 ForeignKey 上自动创建一个数据库索引。

这个子查询问题的关键是索引 JOIN 字段(在我的问题中又名:parcel)。这很简单,看起来像这样:

class OwnershipRecord(Model):
  parcel = CharField(max_length=10, unique=True,
                     db_index=True)
  owner_name = ...
  other data fields ...

ResidentialMasterRecord(Model):
  parcel = CharField(max_length=10, unique=True,
                     db_index=True)
  livablesqft = ...
  lotsqft = ...

用于此的docs 很少,但很容易实现。

db_index

Field.db_index

如果True,将为该字段创建数据库索引。

结果:我的查询从大约 30 分钟缩短到 1.55 秒。


>>> import timeit
>>> from django.db.models import OuterRef, Subquery
>>> from leads.models import Lead
>>> from ownership.models import OwnershipRecord
>>> from mcassessor.models import ResidentialMasterRecord
>>> rmr_sq = ResidentialMasterRecord.objects.filter(parcelid=OuterRef('ownership_record__parcel'))
>>> qs = Lead.objects.select_related('ownership_record').annotate(sqft=Subquery(rmr_sq.values("livablesqft")[:1])).filter(sqft__gte=1700)
>>> toc = timeit.default_timer()
... qs_list = list(qs)
... print(timeit.default_timer() - toc)
[Out] 1.55457401276
>>> len(qs_list)
[Out] 823

【讨论】:

    【解决方案2】:

    (1)即使底层数据库没有fkey关系,也可以在ORM中指定两个表是相关的。
    (2) 就像@bma 提到的那样,索引会对性能产生很大影响。

    但是,总的来说,我的策略是——将您的子查询分成两个单独的查询,并将一些数据保留在内存中。

    例如,

    def chunkify(rg, chunk_size=1000):
        while rg:
            yield rg[:chunk_size]
            rg = rg[chunk_size:]
    
    min_sq_footage = 1500
    master_records = ResidentialMasterRecords.objects.filter(sqft__gte=min_sq_footage)
    parcels = list(master_records.values_list('parcel', flat=True))
    for parcel_chunk in chunkify(parcels):
        qs = Lead.objects.select_related('ownership_record').filter(ownership_record__parcel__in=parcel_chunk)
        # do some work
    

    【讨论】:

    • 我喜欢这个答案 - 我几乎一直都在使用这种技术。在这种情况下,我很难实现它。 原因如下:此子查询问题是 20 输入过滤表单的一部分,其中使用 AJAX 获取/返回结果。所以我根据前端的用户输入动态链接过滤器。请求/可用的结果数量范围为 10 - 200,000。一种实现方式是流式 AJAX 响应无限滚动。就目前而言,那是一座太远的桥梁。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    • 2019-06-28
    • 1970-01-01
    • 2015-12-03
    • 1970-01-01
    相关资源
    最近更新 更多