提高 django 查询集性能

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

我需要从 API 中获取一组产品及其各自的折扣。最初,我使用

subqueries
方法来实现此目的,但随着数据集的增长,它导致了严重的性能问题。我正在寻找有关如何重构查询集以提高性能的建议。任何有效检索产品折扣的想法或替代方法将不胜感激。

这是查询集:

active_vouchers = Voucher.dal.get_active_vouchers(
    voucher_kind=VoucherKindChoices.Static_based
).prefetch_related('voucher_ranges__packs__expense')

is_discounted_subquery = active_vouchers.filter(
    voucher_ranges__packs__product__id=OuterRef('pk'), min_basket_value=0
    )

voucher_type = is_discounted_subquery.filter(voucher_ranges__packs__product__id=OuterRef('pk')).\
    values('voucher_type')[:1]

voucher_kind = is_discounted_subquery.filter(voucher_ranges__packs__product__id=OuterRef('pk')).\
    values('voucher_kind')[:1]

voucher_fixed_amount = Voucher.bll.convert_voucher_fixed_price_currency(active_vouchers, currency.code).\
    filter(voucher_ranges__packs__product__id=OuterRef('pk'),
).values('converted_fixed_price')[:1]

voucher_percent_amount = is_discounted_subquery.filter(
    voucher_ranges__packs__product__id=OuterRef('pk'),
).values('value_percentage_based')[:1]

qs = self.annotate(
    is_discounted=Case(
        When(
            Q(Exists(is_discounted_subquery)),
            then=True,
        ),
        default=False
    ),
    voucher_kind=Case(
        When(
            Q(is_discounted=True),
            then=Subquery(voucher_kind)
        ),
        default=Value('no_voucher')
    ),
    voucher_type=Case(
        When(
            Q(is_discounted=True),
            then=Subquery(voucher_type)
        ),
        default=Value('no_voucher')
    ),
    fixed_static_discount=Case(
    When(Q(
            is_discounted=True,
            voucher_kind=VoucherKindChoices.Static_based,
            voucher_type=VoucherTypeChoices.Fixed_price_based
            ),
        then=Subquery(voucher_fixed_amount)
        ),
    default=0,
    output_field=MoneyCurrencyOutput(currency)
    ),
    percentage_static_discount=Case(
    When(Q(
            is_discounted=True,
            voucher_kind=VoucherKindChoices.Static_based,
            voucher_type=VoucherTypeChoices.Percentage_based
            ),
        then=Subquery(voucher_percent_amount)
        ),
    default=0,
    ),
    price_after_static_discount=Case(
        When(
            Q(
                is_discounted=True,
                voucher_kind=VoucherKindChoices.Static_based,
                voucher_type=VoucherTypeChoices.Fixed_price_based),
            then=ExpressionWrapper(
                F('default_pack_price') - F('fixed_static_discount'),
                output_field=MoneyCurrencyOutput(currency),
            )
        ),
        When(
            Q(
                is_discounted=True,
                voucher_kind=VoucherKindChoices.Static_based,
                voucher_type=VoucherTypeChoices.Percentage_based),
            then=ExpressionWrapper(
                F('default_pack_price') * (100 - F('percentage_static_discount')) / 100,
                output_field=MoneyCurrencyOutput(currency),
            )
        ),
    default=0,
    output_field=MoneyCurrencyOutput(currency)
    ),
)
return qs

这是 QuerySet 的解释和 SQL 中的查询:

https://explain.depesz.com/s/xIgD#source

我不确定为什么计划时间花费太长的时间,问题可能是因为我在 docker 上的 postgres 上的配置,所以这是我使用的配置:

jit = off

# Memory Configuration
shared_buffers = 512MB
effective_cache_size = 2GB
work_mem = 5MB
maintenance_work_mem = 102MB

# Checkpoint Related Configuration
min_wal_size = 2GB
max_wal_size = 3GB
checkpoint_completion_target = 0.9
wal_buffers = -1

# Network Related Configuration
listen_addresses = '*'
max_connections = 1000

# Storage Configuration
random_page_cost = 1.1
effective_io_concurrency = 200

# Worker Processes Configuration
max_worker_processes = 8
max_parallel_workers_per_gather = 2
max_parallel_workers = 2

更新1: 这些是型号:

优惠券范围:

class VoucherRange(TimeStampMixin, TruncateMixin):
    voucher = models.OneToOneField(
        'Voucher',
        verbose_name=_('voucher'),
        related_name='voucher_ranges',
        on_delete=models.CASCADE,
        help_text=_('The voucher this voucher range belongs to.')
    )
    packs = models.ManyToManyField(
        'warehouse.Pack',
        verbose_name=_('packs'),
        related_name='voucher_ranges',
        help_text=_('Access to the related pack(s) of a voucher range.'),
        blank=True
    )

优惠券:

class Voucher(TitleSlugDescriptionMixin, TimeStampMixin, TruncateMixin):
    voucher_kind = models.CharField(
        _('voucher kind'),
        max_length=20,
        validators=[MaxLengthValidator(20)],
        choices=VoucherKindChoices.choices,
        help_text=_('Determines what kind of voucher is.')
    )
    voucher_type = models.CharField(
        _('voucher type'),
        max_length=20,
        validators=[MaxLengthValidator(20)],
        choices=VoucherTypeChoices.choices,
        help_text=_('Determines what type of voucher is.')
    )
    status = models.CharField(
        _('status'),
        max_length=20,
        validators=[MaxLengthValidator(20)],
        choices=VoucherStatusChoices.choices,
        help_text=_('Status of the voucher.')
    )
    value_fixed_price_based = MoneyField(
        _('fixed price based'),
        max_digits=14,
        decimal_places=2,
        default_currency=DEFAULT_CURRENCY_SHOW_ON_SITE,
        null=True,
        blank=True,
        help_text=_('If the voucher type is fixed price based, '
                    'it takes the value of the money type.')
    )
    value_percentage_based = models.PositiveIntegerField(
        _('percentage based'),
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        default=0,
        help_text=_('If the voucher type is percentage based, '
                    'takes the value between 0 to 100 percent.')
    )
    start_time = models.DateTimeField(
        _('start datetime'),
        help_text=_('The release date or the start of the voucher.')
    )
    end_time = models.DateTimeField(
        _('end datetime'),
        help_text=_('The expiry date or the end of the voucher.')
    )

然后我有

Pack
Expense
模型,在凭证 QuerySet 之前,我调用另一个函数从
default_pack_price
模型中为我的 qs 中的每个
Expense
实例获取
Pack

更新2:

我的

get_active_vouchers

def get_unexpired_voucher(self):
    current_time = timezone.now().replace(minute=0, second=0, microsecond=0)
    return self.filter(start_time__lte=current_time, end_time__gte=current_time)

def get_active_vouchers(self,
                        voucher_kind=VoucherKindChoices.Static_based):
    return self.get_voucher_status(VoucherStatusChoices.Open).\
                get_unexpired_voucher().\
                filter(voucher_kind=voucher_kind)

任何我的

convert_voucher_fixed_price_currency

def convert_voucher_fixed_price_currency(self, active_vouchers, currency: str) -> QuerySet:
    if any(x in ['makemigrations', 'migrate', 'reset'] for x in sys.argv):
        return active_vouchers.annotate(
        converted_fixed_price=Value(0)
        )

    currencies = active_vouchers.values_list("value_fixed_price_based_currency", flat=True)
    # todo : user the base function (class) for converting currency
    converted_rates_dict = {
        _currency: convert_currency(input_currency=_currency,
                                    output_currency=currency,
                                    only_return_rate=True
                                ) for _currency in currencies}

    # for those that are the same currency with the given currency
    converted_rates_dict.update({currency: 1})

    case_when_list = list()

    for dict_currency, rate in converted_rates_dict.items():
        case_when_list.append(
                        When(
                            Q(value_fixed_price_based_currency=dict_currency) and \
                            Q(voucher_type__iexact=VoucherTypeChoices.Fixed_price_based),
                            then=F('value_fixed_price_based') * rate),
        )

    return active_vouchers.annotate(
        converted_fixed_price=Case(
            *case_when_list,
            output_field=MoneyCurrencyOutput(currency),
            default=0
        ))
django postgresql django-queryset
© www.soinside.com 2019 - 2024. All rights reserved.