Почему дублирование условия фильтра Django изменяет оценку плана запроса PostgreSQL?

Я работаю над проектом Django с PostgreSQL и реализовал пользовательский фильтр для отбора «активных» записей. Фильтр определен следующим образом:

def get_custom_filter(qs):
    return qs.filter(
        Q(
            Q(timestamp_field__lte=timezone.now(), related_obj__delay__isnull=True)
            | Q(
                related_obj__delay__lte=timezone.now(),
                type_obj__flag=False,
                timestamp_field__isnull=False,
            )
            | Q(type_obj__flag=True, timestamp_field__lte=timezone.now())
        )
    )

Затем я создаю бэкэнд пользовательского фильтра:

class CustomFilterBackend(filters.BaseFilterBackend):
    def filter_queryset(self, request, qs, view):
        return get_custom_filter(qs)

В моем представлении я включаю этот фильтр в список filter_backends. Вот что происходит:

Когда я включаю CustomFilterBackend только один раз, план запроса PostgreSQL показывает, что после фильтрации предполагаемое количество строк для сортировки/уникализации составляет около 7 200 строк. Например, предложение WHERE выглядит так:

WHERE ("tenant_table"."tenant_id" IN (1)
       AND (("main_table"."delay_field" IS NULL
             AND "primary_table"."timestamp_field" <= TIMESTAMP)
            OR (NOT "type_table"."flag"
                AND "main_table"."delay_field" <= TIMESTAMP
                AND "primary_table"."timestamp_field" IS NOT NULL)
            OR ("type_table"."flag"
                AND "primary_table"."timestamp_field" <= TIMESTAMP))
       AND NOT ( ... ))

Когда я дублирую фильтр (включаю его дважды), план показывает расчетное количество строк около 2 400 - примерно треть от первоначальной оценки. Предложение WHERE теперь содержит две копии условия активного фильтра:

WHERE ("tenant_table"."tenant_id" IN (1)
       AND (("main_table"."delay_field" IS NULL
             AND "primary_table"."timestamp_field" <= TIMESTAMP)
            OR (NOT "type_table"."flag"
                AND "main_table"."delay_field" <= TIMESTAMP
                AND "primary_table"."timestamp_field" IS NOT NULL)
            OR ("type_table"."flag"
                AND "primary_table"."timestamp_field" <= TIMESTAMP))
       AND (("main_table"."delay_field" IS NULL
             AND "primary_table"."timestamp_field" <= TIMESTAMP)
            OR (NOT "type_table"."flag"
                AND "main_table"."delay_field" <= TIMESTAMP
                AND "primary_table"."timestamp_field" IS NOT NULL)
            OR ("type_table"."flag"
                AND "primary_table"."timestamp_field" <= TIMESTAMP))
       AND NOT ( ... ))

Логично, что повторение одного и того же условия (т. е. X AND X) не должно изменить набор результатов. Однако планировщик PostgreSQL умножает селективность, когда видит условие дважды - если одно применение фильтра оценивается как пропускающее 50 % строк, то дублирование оценит 0,5 × 0,5 = 25 % строк. Это значительно сокращает количество строк для последующих операций (таких как сортировка и уникализация) и приводит к снижению общей оценки затрат.

Мои вопросы таковы:

  1. Верно ли, что PostgreSQL умножает селективность, когда одно и то же условие дважды встречается в предложении WHERE?
  2. Как PostgreSQL рассчитывает селективность в этих случаях?
  3. Существует ли рекомендуемый способ заставить оптимизатор получить ту же самую нижнюю оценку строки без дублирования условия фильтрации в коде?
Вернуться на верх