You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅对Django Postgres全文搜索分页结果执行SearchHeadline优化性能

实现方案

核心逻辑是拆分查询为两个阶段:第一阶段仅执行过滤、排序、去重、分页,拿到当前页的少量结果,第二阶段仅对这部分结果计算高亮,避免全量执行高开销的ts_headline。

方式1:子查询嵌套(最贴近你需要的SQL结构)

Django 3.2及以上版本可以直接通过子查询嵌套生成你预期的SQL结构:

from django.contrib.postgres.search import SearchQuery, SearchRank, SearchHeadline
from django.db.models import F, Q, Func

# 1. 构造基础查询:仅做过滤、排序、分页,不计算高亮
search_term = "hearing"
search_query = SearchQuery(search_term, config="public.bb")

# 基础查询:保留你原有的所有过滤、rank计算、排序、去重逻辑,仅移除高亮相关注解
base_qs = (
    DocketsDocument.objects.filter(
        search_vector__contains=search_query,
        # 其余过滤条件原样保留
    )
    .annotate(
        rank=SearchRank(F("search_vector"), search_query)
        # 你原有的多表关联rank加权计算逻辑原样保留
    )
    .order_by("-rank", "id")
    .distinct()
    .values("id", "title", "content")  # 仅查询生成高亮必需的字段
    [:25]  # 分页逻辑,使用Paginator时替换为对应页的切片即可
)

# 2. 外层查询仅对分页后的结果计算高亮
final_qs = (
    DocketsDocument.objects.filter(id__in=base_qs.values("id"))
    .annotate(
        title_snippet=SearchHeadline(
            "title",
            search_query,
            config="public.bb",
            highlight_all=True,
            start_sel="<mark>",
            stop_sel="</mark>",
        ),
        content_snippet=SearchHeadline(
            Func(F("content"), function="array_to_string", template="%(function)s(%(expressions)s, ' ', '')"),
            search_query,
            config="public.bb",
            max_fragments=5,
            start_sel="<mark>",
            stop_sel="</mark>",
        )
    )
    # 按基础查询的rank排序,保证结果顺序正确
    .order_by("-rank", "id")
)

生成的SQL会和你手动优化的结构完全一致,ts_headline仅会在外层对25条分页结果执行。

方式2:先取分页结果再单独计算高亮(更简单易维护)

如果不需要严格生成嵌套子查询SQL,也可以先拿到当前页的25条记录实例,再单独为这部分记录生成高亮,性能完全相同:

# 第一步:取当前页的基础数据,无高亮
paginator = Paginator(base_qs, 25)
current_page = paginator.page(page_num)
page_items = list(current_page.object_list)

# 第二步:仅对当前页的25条数据生成高亮
for item in page_items:
    item.title_snippet = SearchHeadline(
        item.title,
        search_query,
        config="public.bb",
        highlight_all=True,
        start_sel="<mark>",
        stop_sel="</mark>",
    )
    # 同理生成content_snippet

额外优化:消除DISTINCT提升性能

你提到DISTINCT是性能瓶颈之一,这是因为左连接关联多个表时会产生重复行,你可以将关联表的匹配逻辑改为Exists子查询,避免不必要的JOIN,自然消除重复行:

from django.db.models import Exists, OuterRef

# 构造关联表匹配的Exists子查询
attachment_match = Exists(
    DocketsAttachment.objects.filter(
        main_document_id=OuterRef("id"),
        search_vector__contains=search_query
    )
)
table_match = Exists(
    DocketsTable.objects.filter(
        object_id=OuterRef("id"),
        content_type_id=8,
        search_vector__contains=search_query
    )
)

# 基础查询过滤条件替换为Exists,无需JOIN,无需DISTINCT
base_qs = DocketsDocument.objects.filter(
    Q(search_vector__contains=search_query) | attachment_match | table_match,
    # 其余过滤条件保留
)
# 后续逻辑不变,可直接删除.distinct()调用

内容的提问来源于stack exchange,提问作者Ilya Voytov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 10:57:04