使用内部循环,数据库性能问题(实体 - 属性 - 值模型)循环使用get_or_create

时间:2018-04-10 09:26:53

标签: django database performance

我有以下Django模型:

class Property(models.Model):
    name = models.TextField(unique=True, db_index=True)


class AirtableProduct(models.Model):
    internal_id = models.IntegerField(unique=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class AirtableProductPropertyValue(models.Model):
    airtable_product = models.ForeignKey(AirtableProduct, on_delete=models.CASCADE, related_name='properties')
    property = models.ForeignKey(Property, on_delete=models.CASCADE)
    value = models.TextField()

    class Meta:
        unique_together = (('airtable_product', 'property'),)
        index_together = [
           'airtable_product', 'property'
        ]

它们表示一个由internal_id标识的对象(AirtableProduct),以及我用Property和AirtableProductPropertyValue表表示的可变数量的属性。

在创建/更新产品及其属性时,问题出现在以下代码中:

// products is an array of dictionary with the properties as key/value
def insert_all_airtable_products(products):
    logger.info('Inserting Airtable products into the database')

    for product in products:
        internal_id = product.pop('Internal ID')
        ap, created = AirtableProduct.objects.get_or_create(internal_id=internal_id)

        for field in product:
            property, created = Property.objects.get_or_create(
                name=strip_spaces(field)
            )

            ap_pv, created = AirtableProductPropertyValue.objects.get_or_create(airtable_product=ap, property=property)
            ap_pv.value = strip_spaces(product[field])
            ap_pv.save()

我有10,000个产品,每个产品都有20多个属性,因为我必须遍历每个产品并更新/创建旧/新属性,这项操作的成本很高,需要花费大量时间数据库。

怎样才能让它变得更好?

1 个答案:

答案 0 :(得分:0)

您可以从数据库中获取所有AirtableProducts及其AirtableProductPropertyValue和Property,将它们存储在字典中并使用它们代替get_or_create。

您也可以使用bulk_create而不是每次都调用object.save()。以下代码说明了它:

airtable_product_dict = {}
queryset = AirtableProduct.objects.prefetch_related(
'properties', 'properties__property')

for airtable_product in queryset:
    airtable_product_dict[airtable_product.internal_id] = airtable_product

new_aritable_products = []

for product in products:
    internal_id = product.pop('Internal ID')
    if internal_id not in aritable_product_dict:
        new_airtable_products.append(AirtableProduct(internal_id))

    # Follow similar logic ahead

AirtableProduct.objects.bulk_create(new_airtable_products)

注意:

  1. 使用bulk_create时,不会填充auto_now_add和auto_add字段,请考虑在构造函数中添加它们。
  2. 此代码不是线程安全的,如果两个线程同时运行,则可能会出现不一致状态。
  3. 考虑阅读Django db optimization