我需要从 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
))